Merge pull request #616 from Safelite/SSR-1160-add-desktop-functionality

SSR-1160 add desktop functionality
This commit is contained in:
bmauger 2024-04-22 13:48:22 -04:00 committed by GitHub
commit 39a1ec1416
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 3886 additions and 3052 deletions

2
package-lock.json generated
View file

@ -37,7 +37,7 @@
"@vue/vue3-jest": "^27.0.0-alpha.1",
"axios-mock-adapter": "^1.21.5",
"babel-jest": "^27.0.6",
"eslint": "8.45.0",
"eslint": "^8.45.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-import-resolver-alias": "1.1.2",
"eslint-plugin-import": "2.26.0",

View file

@ -44,7 +44,7 @@
"@vue/vue3-jest": "^27.0.0-alpha.1",
"axios-mock-adapter": "^1.21.5",
"babel-jest": "^27.0.6",
"eslint": "8.45.0",
"eslint": "^8.45.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-import-resolver-alias": "1.1.2",
"eslint-plugin-import": "2.26.0",
@ -61,4 +61,4 @@
"vitest": "^0.33.0",
"volar-service-vetur": "latest"
}
}
}

View file

@ -37,8 +37,8 @@ const widgetFields = Object.freeze({
NAME: 'Name',
BACK_BUTTON_TEXT: 'BackButtonText',
FORWARD_BUTTON_TEXT: 'ForwardButtonText',
FOOTER_IMAGE: 'FooterImage',
ALT_TEXT: 'AltText'
ALT_TEXT: 'AltText',
FOOTER_IMAGE_URL: 'FooterImageURL'
}
});

View file

@ -1,13 +1,18 @@
<!-- Documented in confluence https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Component -->
<template>
<div
:class="isOverflowScrollable
? 'button-question button-question-overflow'
: 'button-question'">
:class="
isOverflowScrollable
? 'button-question button-question-overflow'
: 'button-question'
">
<div
v-if="questionText && answers && answers.length > 0"
class="question-text d-flex"
:class="{'small-question-text': isSmallQuestionText,'small-question-label-text':isSmallQuestionLabelText}">
:class="{
'small-question-text': isSmallQuestionText,
'small-question-label-text': isSmallQuestionLabelText,
}">
<span class="fw-bold w-100">{{ questionText }}</span>
</div>
<div class="w-100 d-flex justify-content-center">
@ -22,9 +27,11 @@
:data-focus-target="formatString(groupName)"
tabindex="-1">
{{ questionText }}
{{ isMultiSelect && answers && answers.length > 1
? "Select one or more options below."
: "Select an option below." }}
{{
isMultiSelect && answers && answers.length > 1
? 'Select one or more options below.'
: 'Select an option below.'
}}
</legend>
<div :class="getComponentLoopWrapperClasses">
<div
@ -54,12 +61,12 @@
:setLastValuePushedToGa="setLastValuePushedToGa"
:suppressError="suppressError" />
<!-- For nested questions -->
<transition
name="fade"
mode="out-in">
<transition name="fade" mode="out-in">
<div
v-if="typeof selectedValues == 'string' &&
selectedValues == answer.value">
v-if="
typeof selectedValues == 'string' &&
selectedValues == answer.value
">
<slot></slot>
</div>
</transition>
@ -67,11 +74,8 @@
</div>
</fieldset>
</div>
<div
v-if="!suppressError"
class="row form-test-error mt-1">
<error-message
:name="formatString(groupName)"></error-message>
<div v-if="!suppressError" class="row form-test-error mt-1">
<error-message :name="formatString(groupName)"></error-message>
</div>
</div>
</template>
@ -92,16 +96,16 @@ export default {
listCard,
ErrorMessage,
radio,
providerPrefRadio
providerPrefRadio,
},
props: {
buttonTypeString: {
type: String,
default: 'listButton'
default: 'listButton',
},
buttonTypeObject: {
type: Object,
default: null
default: null,
},
isMultiSelect: Boolean,
groupName: String,
@ -109,16 +113,16 @@ export default {
answers: Array,
textPosition: {
type: String,
default: 'text-center'
default: 'text-center',
},
selectingInitiatesLoad: Boolean,
loaderColor: {
type: String,
default: 'blue'
default: 'blue',
},
loaderPosition: {
type: String,
default: 'right'
default: 'right',
},
isRequired: Boolean,
isOverflowScrollable: Boolean,
@ -133,7 +137,7 @@ export default {
additionalButtonData: Object,
additionalButtonStyling: String,
isSmallQuestionText: Boolean,
isSmallQuestionLabelText: Boolean
isSmallQuestionLabelText: Boolean,
},
emits: ['update:modelValue'],
setup(props) {
@ -142,11 +146,18 @@ export default {
const fieldOptions = {
value: modelValue,
initialValue: null
initialValue: null,
};
const { errorMessage, handleBlur, handleChange, meta, validate, errors, resetField } =
useField(props.groupName, props.validationRules, fieldOptions);
const {
errorMessage,
handleBlur,
handleChange,
meta,
validate,
errors,
resetField,
} = useField(props.groupName, props.validationRules, fieldOptions);
return {
errorMessage,
@ -155,12 +166,12 @@ export default {
validate,
meta,
errors,
resetField
resetField,
};
},
data() {
return {
lastValuePushedToGa: null
lastValuePushedToGa: null,
};
},
computed: {
@ -169,8 +180,8 @@ export default {
const baseClasses = this.isOverflowScrollable
? 'container-fluid overflow-scroll position-absolute px-5 pt-1 py-0'
: this.buttonTypeString === 'listCard'
? 'w-100'
: '';
? 'w-100'
: '';
const SmallQuestionTextClass = this.isSmallQuestionText
? `${baseClasses}small-question-text`
@ -191,7 +202,8 @@ export default {
classes = 'd-flex flex-row p-0';
break;
case 'listCard':
classes = 'row g-2 justify-content-center';
classes =
'row g-2 g-md-5 justify-content-center mb-1 flex-nowrap';
if (this.isWide) {
classes += ' flex-column';
}
@ -226,25 +238,50 @@ export default {
break;
default:
}
// Determine number of buttons and add class accordingly.
// This is to accommodate unique spacing per design specs.
if (
this.buttonTypeString === 'listCard' &&
this.buttonsInfo.length > 2
) {
classes += ' two-list-card-width';
}
if (
this.buttonTypeString === 'listCard' &&
this.buttonsInfo.length === 1
) {
classes += ' one-list-card-width';
}
return classes;
},
buttonsInfo() {
return (Array.isArray(this.answers) ? this.answers : [])?.map((answer) => ({
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
altText: answer.altText ?? (answer.Name ? answer.Name : answer),
buttonLabelSubCopy: answer.buttonLabelSubCopy ?? answer.SubText,
buttonBodyCopy: answer.buttonBodyCopy ?? answer.buttonBodyCopy,
buttonAuxiliaryCopy: answer.buttonAuxiliaryCopy ?? answer.buttonAuxiliaryCopy,
buttonFooterCopy: answer.buttonFooterCopy ?? answer.buttonFooterCopy,
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
buttonImageId: answer.buttonImageId ?? answer.ImageId,
groupName: this.formatString(this.groupName),
value:
answer.value
?? (this.useTextForValue && answer.Text ? answer.Text : answer.Name)
?? (typeof answer !== 'object' ? answer : null)
}));
return (Array.isArray(this.answers) ? this.answers : [])?.map(
(answer) => ({
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
altText:
answer.altText ?? (answer.Name ? answer.Name : answer),
buttonLabelSubCopy:
answer.buttonLabelSubCopy ?? answer.SubText,
buttonBodyCopy:
answer.buttonBodyCopy ?? answer.buttonBodyCopy,
buttonAuxiliaryCopy:
answer.buttonAuxiliaryCopy ??
answer.buttonAuxiliaryCopy,
buttonFooterCopy:
answer.buttonFooterCopy ?? answer.buttonFooterCopy,
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
buttonImageId: answer.buttonImageId ?? answer.ImageId,
groupName: this.formatString(this.groupName),
value:
answer.value ??
(this.useTextForValue && answer.Text
? answer.Text
: answer.Name) ??
(typeof answer !== 'object' ? answer : null),
})
);
},
selectedValues: {
get() {
@ -252,24 +289,25 @@ export default {
},
set(selectedAnswers) {
this.$emit('update:modelValue', selectedAnswers);
}
}
},
},
},
watch: {
modelValue(newValue) {
this.resetField({
value: newValue
value: newValue,
});
}
},
},
beforeMount() {
if (this.buttonTypeObject) {
this.$options.components[this.buttonTypeString] = this.buttonTypeObject;
this.$options.components[this.buttonTypeString] =
this.buttonTypeObject;
}
},
beforeUnmount() {
this.resetField({
value: ''
value: '',
});
},
methods: {
@ -278,8 +316,8 @@ export default {
},
setLastValuePushedToGa(lastValuePushedToGa) {
this.lastValuePushedToGa = lastValuePushedToGa;
}
}
},
},
};
</script>
@ -325,17 +363,17 @@ export default {
.small-question-label-text {
&.question-text {
span {
text-align: left;
margin: 0 0 0.25rem 0;
}
}
&.question-text {
margin: 0;
}
&fieldset {
.ui-radio {
margin: 0;
}
}
text-align: left;
margin: 0 0 0.25rem 0;
}
}
&.question-text {
margin: 0;
}
&fieldset {
.ui-radio {
margin: 0;
}
}
}
</style>

View file

@ -6,7 +6,10 @@
tabindex="-1"
aria-labelledby="ModalComponentLabel"
aria-hidden="true"
v-on="{ 'hidden.bs.modal': onModalClosed, 'shown.bs.modal': onModalOpened }">
v-on="{
'hidden.bs.modal': onModalClosed,
'shown.bs.modal': onModalOpened,
}">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header mb-2 mt-2">
@ -33,7 +36,10 @@
class="w-100"
loaderColor="white"
:buttonText="footerButtonText"
:class="(isButtonDisabled || isFooterButtonDisabled) && 'form-test-invalid'"
:class="
(isButtonDisabled || isFooterButtonDisabled) &&
'form-test-invalid'
"
@clickEvent="validateAndEmit" />
</div>
</div>
@ -50,23 +56,25 @@ export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'modal',
components: {
modalButtonMain
modalButtonMain,
},
props: {
modalId: String,
headerText: String,
footerButtonText: String,
onModalOpenedCallback: {
type: Function
type: Function,
},
onModalClosedCallback: {
type: Function
type: Function,
},
isButtonDisabled: Boolean
isButtonDisabled: Boolean,
},
emits: ['footer-button-event', 'isModalOpened'],
setup(props) {
const modalId = props.modalId ? props.modalId : `modal-${crypto.randomUUID()}`;
const modalId = props.modalId
? props.modalId
: `modal-${crypto.randomUUID()}`;
const { meta, validate, resetForm } = useForm();
@ -75,7 +83,7 @@ export default {
modalId,
meta,
validate,
resetForm
resetForm,
};
},
computed: {
@ -84,7 +92,7 @@ export default {
return !this.meta.valid;
}
return !this.meta.dirty || !this.meta.valid;
}
},
},
methods: {
async validateAndEmit() {
@ -106,38 +114,34 @@ export default {
this.onModalClosedCallback?.();
},
openModal() {
const modal = Modal.getOrCreateInstance(document.getElementById(this.modalId));
const modal = Modal.getOrCreateInstance(
document.getElementById(this.modalId)
);
modal?.show();
this.$emit('isModalOpened', true);
},
closeModal() {
const modal = Modal.getInstance(document.getElementById(this.modalId));
const modal = Modal.getInstance(
document.getElementById(this.modalId)
);
modal?.hide();
this.$emit('isModalOpened', false);
}
}
},
},
};
</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,10 +149,13 @@ 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;
font-size: 1.25rem;
line-height: 32px;
color: $black;
}
}
@ -157,17 +164,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 +200,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,51 +1,45 @@
<template>
<div :class="`page-container-grouped-styles questions-page`">
<div class="fade-on-route-transition position-relative">
<loadingModal
ref="loadingModal"
:textSlides="loadingText" />
<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" />
<alert
ref="alertFewMoreQuestions"
cmsWidgetName="alertWidget"
alertClass="alert-warning"
:manualHeadline="alertFewMoreQuestionsHeader"
:manualCopy="alertFewMoreQuestionsCopy"
:isDismissible="false"
class="mt-5" />
<div
v-for="(questionsDatum, i) in questionsData"
:key="questionsDatum.key">
<questionChain
v-if="showThisQuestionChain(questionsDatum, i)"
ref="questionChain"
v-model="selectedAnswers[questionsDatum.answerKey]"
:questionData="questionsDatum.questions"
:index="i"
:answerKey="questionsDatum.answerKey"
:validationRules="validationRules" />
</div>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!isMetaValid"
@backClicked="handleBackButtonAction"
@ForwardClicked="handleForwardButtonAction" />
</div>
</div>
</div>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6 px-0 px-md-2">
<loadingModal ref="loadingModal" :textSlides="loadingText" />
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<alert
ref="alertFewMoreQuestions"
cmsWidgetName="alertWidget"
alertClass="alert-warning"
:manualHeadline="alertFewMoreQuestionsHeader"
:manualCopy="alertFewMoreQuestionsCopy"
:isDismissible="false"
class="mt-5" />
<div
v-for="(questionsDatum, i) in questionsData"
:key="questionsDatum.key">
<questionChain
v-if="showThisQuestionChain(questionsDatum, i)"
ref="questionChain"
v-model="selectedAnswers[questionsDatum.answerKey]"
:questionData="questionsDatum.questions"
:index="i"
:answerKey="questionsDatum.answerKey"
:validationRules="validationRules" />
</div>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!isMetaValid"
@backClicked="handleBackButtonAction"
@ForwardClicked="handleForwardButtonAction" />
</div>
</div>
</div>
@ -70,7 +64,7 @@ export default {
questionChain,
siteSubHeader,
siteFooter,
loadingModal
loadingModal,
},
props: {
isMetaValid: Boolean,
@ -80,7 +74,7 @@ export default {
validationRules: String,
modelValue: Object,
loadingText: Array,
index: Number
index: Number,
},
emits: ['update:modelValue', 'forwardButtonAction', 'back-click'],
computed: {
@ -90,16 +84,22 @@ export default {
},
set(newValue) {
this.$emit('update:modelValue', newValue);
}
}
},
},
},
methods: {
showThisQuestionChain(glass, i) {
// return false if no questions or if suppressed
if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) {
if (
!glass.questions ||
glass.questions?.length < 1 ||
glass.isSuppressedPart
) {
return false;
}
return this.index === i || glass.answerData?.answerResult?.length > 0;
return (
this.index === i || glass.answerData?.answerResult?.length > 0
);
},
handleForwardButtonAction() {
this.$emit('forwardButtonAction');
@ -109,8 +109,8 @@ export default {
},
showLoadingModal() {
this.$refs.loadingModal.showModal();
}
}
},
},
};
</script>

View file

@ -0,0 +1,38 @@
<template>
<div class="footerImage">
<img :src="footerImageURL" />
</div>
</template>
<script>
import widgetFields from '@/constants/cms-widget-fields.js';
export default {
name: 'footer-image',
data() {
return {
widget: {
footer: 'SiteFooterWidget',
},
};
},
computed: {
footerImageURL() {
return this.getCmsContent(
this.widget.footer,
widgetFields.FOOTER_WIDGET.FOOTER_IMAGE_URL
);
},
},
};
</script>
<style lang="scss" scoped>
.footerImage {
display: flex;
justify-content: center;
margin-top: auto;
img {
max-width: 23.4375rem;
}
}
</style>

View file

@ -1,25 +1,24 @@
<template>
<div>
<footer
id="infoBox"
class="footer container-fluid g-5 my-5 px-0">
<footer id="infoBox" class="footer container-fluid g-5 my-5 px-0">
<div
class="row d-flex vw-100 mx-0"
:class="[
isStackedVertically
? 'flex-column align-items-stretch'
: 'flex-row-reverse align-items-center'
: 'flex-row-reverse align-items-center',
]">
<div
id="stacked"
class="col button-col d-flex px-0">
<div id="stacked" class="col button-col d-flex px-0">
<buttonMain
v-if="!isForwardButtonHidden"
ref="buttonMain"
isPrimary
:buttonText="buttonText"
loaderColor="white"
:class="(disableForwardAction || isForwardActionDisabled) && 'form-test-invalid'"
:class="
(disableForwardAction || isForwardActionDisabled) &&
'form-test-invalid'
"
:aria-disabled="isForwardActionDisabled"
:isDisabled="isForwardActionDisabled"
data-bs-target="#footerModal"
@ -41,14 +40,6 @@
</div>
</div>
</footer>
<div
v-if="footerImageURL"
id="imgCity"
class="footerImage">
<img
id="siteFooterImage"
:src="footerImageURL" />
</div>
</div>
</template>
@ -61,21 +52,21 @@ export default {
name: 'site-footer',
components: {
textLink,
buttonMain
buttonMain,
},
props: {
isForwardActionDisabled: Boolean,
isBackButtonHidden: { type: Boolean, default: false },
isForwardButtonHidden: { type: Boolean, default: false },
cmsWidgetName: String,
isStackedVertically: { type: Boolean, default: false }
isStackedVertically: { type: Boolean, default: false },
},
emits: ['backClicked', 'forwardClicked'],
data() {
return {
paddingHeight: 0,
customButtontext: '',
disableForwardAction: false
disableForwardAction: false,
};
},
computed: {
@ -88,15 +79,21 @@ export default {
: this.getCmsContent(this.cmsWidgetName, 'ForwardButtonText');
},
footerImageURL() {
return this.getCmsContent(this.cmsWidgetName, 'FooterImageURL');
const output = this.getCmsContent(
this.cmsWidgetName,
'FooterImageURL'
);
return output;
},
includeFooterImage() {
const currentPageName = this.getPageNameByQueryString();
return (currentPageName.toLowerCase() === issPageValues.WELCOME_PAGE);
}
return currentPageName.toLowerCase() === issPageValues.WELCOME_PAGE;
},
},
mounted() {
this.paddingHeight = this.includeFooterImage ? this.getFooterInfoBoxHeight() + 16 : this.getFooterInfoBoxHeight() + 24;
this.paddingHeight = this.includeFooterImage
? this.getFooterInfoBoxHeight() + 16
: this.getFooterInfoBoxHeight() + 24;
this.$nextTick(() => {
window.addEventListener('resize', this.onResize);
});
@ -131,8 +128,8 @@ export default {
},
enableForwardAction() {
this.disableForwardAction = false;
}
}
},
},
};
</script>
@ -180,21 +177,18 @@ export default {
}
}
}
.footerImage
{
.footerImage {
text-align: center;
position: sticky;
}
@media only screen and (max-width: 340px) {
.footerImage
{
.footerImage {
bottom: 78px;
}
}
#siteFooterImage
{
#siteFooterImage {
width: 100%;
}
</style>

View file

@ -24,12 +24,12 @@ describe('menu-modal.vue', () => {
expect(modalFooter.text()).toContain('Safelite Group');
});
it('Should return Terms of service text link text', async () => {
it('Should return Terms of use text link text', async () => {
// Act
const wrapper = shallowMount(menuModal);
// Expect
expect(wrapper.html()).toContain('Terms of service');
expect(wrapper.html()).toContain('Terms of use');
});
it('Should return "Your privacy choices" text link text', async () => {
@ -40,22 +40,6 @@ describe('menu-modal.vue', () => {
expect(wrapper.html()).toContain('Your privacy choices');
});
test('Icon for link to "Privacy Policies" page is displayed with the correct alternate text', () => {
// NOTE: We need a way to target a specific <textLink> component so we can be sure that
// an icon is coming from a specific <textLink> component.
// That requires an additional prop in the <textLink> component. Will hold off on adding the prop
// until there is more clarity on how we handle shared components.
// For now the test is below is the best we can do.
// Arrange
const wrapper = mount(menuModal);
// Expect
const icon = wrapper.find('[data-id="ccpa-icon"]');
expect(icon.isVisible()).toBe(true);
expect(icon.attributes('alt')).toBe('Your privacy choices');
});
it('Should return Warranty text link text', async () => {
// Act
const wrapper = shallowMount(menuModal);

View file

@ -5,7 +5,21 @@
type="button"
:class="[isActive ? 'active' : '']"
aria-label="Hamburger Menu (modal window)"
@click="toggle()">
@click="toggleModal">
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</button>
</div>
<div class="menu-modal-container">
<button
aria-hidden="true"
tabindex="-1"
class="menu-button"
type="button"
:class="[isActive ? 'active' : '']"
aria-label="Hamburger Menu (modal window)"
@click="toggleModal">
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
@ -18,107 +32,99 @@
data-bs-backdrop="false"
tabindex="-1"
aria-labelledby="footerModalLabel"
aria-hidden="true">
<div class="menu-modal-container">
<button
class="menu-button"
type="button"
:class="[isActive ? 'active' : '']"
aria-label="Hamburger Menu (modal window)"
@click="toggle()">
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</button>
</div>
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header visually-hidden">
<h5
id="footerModalLabel"
class="modal-title">
Footer Navigation
</h5>
aria-hidden="true"
v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header visually-hidden">
<h5 id="footerModalLabel" class="modal-title">
Footer Navigation
</h5>
</div>
<div class="modal-body d-flex flex-column">
<textLink
linkType="newWindowLink"
text="Terms of use"
href="//www.safelite.com/terms-of-use"
target="_blank" />
<textLink
linkType="newWindowLink"
text="Your privacy choices"
href="//www.safelite.com/privacy-center"
target="_blank">
<template #after-text>
<img
class="ccpa-icon"
src="~@/assets/img/icons/ccpa-icon.svg"
alt="Your privacy choices" />
</template>
</textLink>
<textLink
linkType="newWindowLink"
text="Warranty"
href="//www.safelite.com/national-lifetime-warranty"
target="_blank" />
<textLink
linkType="newWindowLink"
text="Notice at collection"
href="https://www.safelite.com/ccpa-privacy-policy"
target="_blank" />
</div>
<div class="modal-footer d-flex justify-content-start">
&copy; {{ new Date().getFullYear() }} Safelite Group
</div>
</div>
<div class="modal-body d-flex flex-column">
<textLink
linkType="navigation"
text="Terms of service"
href="//www.safelite.com/terms-of-use"
target="_blank" />
<textLink
linkType="navigation"
text="Your privacy choices"
href="//www.safelite.com/privacy-center"
target="_blank">
<template #after-text>
<img
class="ccpa-icon"
data-id="ccpa-icon"
src="~@/assets/img/icons/ccpa-icon.svg"
alt="Your privacy choices" />
</template>
</textLink>
<textLink
linkType="navigation"
text="Warranty"
href="//www.safelite.com/national-lifetime-warranty"
target="_blank" />
<textLink
linkType="navigation"
text="Notice at collection"
href="https://www.safelite.com/ccpa-privacy-policy"
target="_blank" />
</div>
<div class="modal-footer d-flex justify-content-start">
&copy; {{ new Date().getFullYear() }} Safelite Group
</div>
</div>
</div>
</div>
</template>
<script>
import textLink from '@/ux-components/text-link/text-link.vue';
import textLink from '@/ux-components/text-link/text-link';
import { Modal } from 'bootstrap';
import baseMixin from '@/mixins/base-mixin.js';
export default {
name: 'menu-modal',
components: {
textLink
textLink,
},
data() {
return {
isActive: false,
currentFooterAndHeaderHeight: 0
};
},
methods: {
toggle() {
toggleModal() {
if (this.isActive) {
// hide modal
this.isActive = false;
const modal = Modal.getInstance(document.getElementById('footerModal'));
modal?.hide();
this.closeModal();
} else {
// show modal
this.currentFooterAndHeaderHeight = this.getFooterInfoBoxHeight() + 56;
this.isActive = true;
const modal = Modal.getOrCreateInstance(document.getElementById('footerModal'));
modal?.show();
document.querySelector('.fade-on-route-transition').scrollTo({
top: 0, behavior: 'instant'
});
this.openModal();
}
}
}
},
openModal() {
Modal.getOrCreateInstance(
document.getElementById('footerModal')
).show();
},
closeModal() {
Modal.getInstance(document.getElementById('footerModal')).hide();
},
show() {
this.isActive = true;
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
},
hide() {
const self = this;
self.isActive = false;
},
},
};
</script>
<style lang="scss" scoped>
.menu-modal-container {
position: absolute;
padding: 1.47rem 1rem 1.47rem 1.47rem;
padding: 1.47rem 0.5rem 1.47rem 1.47rem;
right: 0;
button {
border: none;
@ -126,14 +132,15 @@ export default {
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2);
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
background-color: $white;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0;//Required to prevent 'squish' on iPhone
padding: 0; //Required to prevent 'squish' on iPhone
z-index: 1050;
.bar1,
.bar2,
.bar3 {
@ -146,7 +153,9 @@ export default {
&.active .bar1 {
transform: rotate(-45deg) translate(-3px, 3px);
}
&.active .bar2 {opacity: 0;}
&.active .bar2 {
opacity: 0;
}
&.active .bar3 {
transform: rotate(45deg) translate(-3px, -3px);
}
@ -155,14 +164,12 @@ export default {
}
.modal {
&.menu-modal {
max-width: 576px;
left: auto;
height: calc(100% - 56px);
top: 56px;
border-top: 1px solid $gray-300;
overflow-x: visible;
overflow-y: visible;
z-index: 3;
.modal-body {
padding: 2rem;
.ccpa-icon {
@ -173,10 +180,6 @@ export default {
}
.modal-fullscreen {
width: 100vw;
max-width: 576px;
}
.navigation-link {
margin-bottom: 1.5rem;
}
.modal-footer {
border-top: none;
@ -184,9 +187,9 @@ export default {
}
.menu-modal-container {
position: absolute;
padding: 1.47rem 1rem 1.47rem 1.47rem;
right: .5rem;
top: -4.5rem;
padding: 1.47rem 0 1.47rem 1.47rem;
right: 0;
top: -5rem;
button {
border: none;
&.menu-button {
@ -196,8 +199,6 @@ export default {
box-shadow: none;
background-color: $white;
position: relative;
right: -.5rem;
top: .5rem;
display: flex;
flex-direction: column;
justify-content: center;

View file

@ -4,69 +4,67 @@
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
ref="vehicleBanner"
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader
ref="siteSubHeader"
cmsWidgetName="SiteSubHeaderWidget"
class="mb-5" />
<alert
v-if="displayVinNotFoundAlert"
ref="alertVinNotFound"
class="mb-4 mt-4"
cmsWidgetName="AlertVinNotFoundWidget"
alertClass="alert-danger"
:isDismissible="false" />
<alert
v-if="displayMatchedDifferentVehicleAlert"
ref="alertMatchedDifferentVehicle"
class="mb-4 mt-4"
:manualHeadline="AlertMatchedDifferentVehicleHeader"
:manualCopy="AlertMatchedDifferentVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<alert
v-if="displayMatchedTwoIdenticalYMMVehicleAlert"
ref="alertMatchedTwoIdenticalYMMVehicle"
class="mb-4 mt-4"
cmsWidgetName="AlertMatchedTwoIdenticalYMMVehicleWidget"
:manualHeadline="AlertMatchedTwoIdenticalYMMVehicleHeader"
:manualCopy="AlertMatchedTwoIdenticalYMMVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<alert
v-if="displayVinLookupByHomeAddressNotAllowedAlert"
ref="alertVinLookupsByHomeAddressNotAllowed"
class="mt-4"
cmsWidgetName="AlertVinLookupsByHomeAddressNotAllowedWidget"
alertClass="alert-danger"
:isDismissible="false" />
<customerQuestions
ref="customerQuestions"
v-model="customerQuestions" />
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isDisabled="!meta.valid"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
</div>
</div>
</div>
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
ref="vehicleBanner"
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader
ref="siteSubHeader"
cmsWidgetName="SiteSubHeaderWidget"
class="mb-5" />
<alert
v-if="displayVinNotFoundAlert"
ref="alertVinNotFound"
class="mb-4 mt-4"
cmsWidgetName="AlertVinNotFoundWidget"
alertClass="alert-danger"
:isDismissible="false" />
<alert
v-if="displayMatchedDifferentVehicleAlert"
ref="alertMatchedDifferentVehicle"
class="mb-4 mt-4"
:manualHeadline="AlertMatchedDifferentVehicleHeader"
:manualCopy="AlertMatchedDifferentVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<alert
v-if="displayMatchedTwoIdenticalYMMVehicleAlert"
ref="alertMatchedTwoIdenticalYMMVehicle"
class="mb-4 mt-4"
cmsWidgetName="AlertMatchedTwoIdenticalYMMVehicleWidget"
:manualHeadline="
AlertMatchedTwoIdenticalYMMVehicleHeader
"
:manualCopy="AlertMatchedTwoIdenticalYMMVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<alert
v-if="displayVinLookupByHomeAddressNotAllowedAlert"
ref="alertVinLookupsByHomeAddressNotAllowed"
class="mt-4"
cmsWidgetName="AlertVinLookupsByHomeAddressNotAllowedWidget"
alertClass="alert-danger"
:isDismissible="false" />
<customerQuestions
ref="customerQuestions"
v-model="customerQuestions" />
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isDisabled="!meta.valid"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
</div>
</div>
@ -89,7 +87,10 @@ import { Form } from 'vee-validate';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import routerParams from '@/router/router-constants/router-params';
import { getDamageString, isGlassAvailableForCarId } from '@/helpers/damage-helper';
import {
getDamageString,
isGlassAvailableForCarId,
} from '@/helpers/damage-helper';
import vinPagesMixin from '@/mixins/vin-pages-mixin';
import { useMainStore } from '@/store/index.js';
@ -104,7 +105,7 @@ export default {
customerQuestions,
alert,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [baseFormMixin, vinPagesMixin],
async beforeRouteEnter(to, from, next) {
@ -115,8 +116,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -137,7 +138,7 @@ export default {
isCarIdDifferent: false,
isSelectedGlassAvailableForVehicle: true,
customAlertData: {},
forwardButtonCarStyle: ''
forwardButtonCarStyle: '',
};
},
computed: {
@ -151,10 +152,14 @@ export default {
const vinYmmFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected =
`${useMainStore().order.vehicle.year} ${useMainStore().order.vehicle.make} ${useMainStore().order.vehicle.model}`;
const vinYmmExpected = `${useMainStore().order.vehicle.year} ${
useMainStore().order.vehicle.make
} ${useMainStore().order.vehicle.model}`;
return this.getCmsContent('AlertMatchedDifferentVehicleWidget', 'BodyText')
return this.getCmsContent(
'AlertMatchedDifferentVehicleWidget',
'BodyText'
)
.replaceAll('{custom:damage}', getDamageString())
.replaceAll('{custom:vinYmmFound}', vinYmmFound)
.replaceAll('{custom:vinYmmExpected}', vinYmmExpected);
@ -171,9 +176,16 @@ export default {
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model} ${this.customAlertData?.vehicleInfo?.style}`;
const vinYmmsExpected =
// eslint-disable-next-line max-len
`${useMainStore().order.vehicle.year} ${useMainStore().order.vehicle.make} ${useMainStore().order.vehicle.model} ${useMainStore().order.vehicle.style}`;
`${useMainStore().order.vehicle.year} ${
useMainStore().order.vehicle.make
} ${useMainStore().order.vehicle.model} ${
useMainStore().order.vehicle.style
}`;
return this.getCmsContent('AlertMatchedTwoIdenticalYMMVehicleWidget', 'BodyText')
return this.getCmsContent(
'AlertMatchedTwoIdenticalYMMVehicleWidget',
'BodyText'
)
.replaceAll('{custom:damage}', getDamageString())
.replaceAll('{custom:vinYmmsFound}', vinYmmsFound)
.replaceAll('{custom:vinYmmsExpected}', vinYmmsExpected);
@ -182,21 +194,24 @@ export default {
const vinYmmFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected =
`${useMainStore().order.vehicle.year} ${useMainStore().order.vehicle.make} ${useMainStore().order.vehicle.model}`;
return (vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase());
}
const vinYmmExpected = `${useMainStore().order.vehicle.year} ${
useMainStore().order.vehicle.make
} ${useMainStore().order.vehicle.model}`;
return vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase();
},
},
watch: {
customerQuestions: {
handler() {
// 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.siteFooter.updateButtonText(this.getCmsContent('siteFooterWidget', 'ForwardButtonText'));
// 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.siteFooter.updateButtonText(
this.getCmsContent('siteFooterWidget', 'ForwardButtonText')
);
this.resetWarningsAndErrors();
},
deep: true
}
deep: true,
},
},
mounted() {
this.attachCustomEvents();
@ -224,23 +239,24 @@ export default {
const vinLookupResponse = useMainStore().lookupVinByAddress({
licenseLastName: this.customerQuestions.lastName,
licenseStreetAddress: this.customerQuestions.addressQuestions.streetAddress,
licenseStreetAddress:
this.customerQuestions.addressQuestions.streetAddress,
licenseZip: this.customerQuestions.addressQuestions.zipCode,
licenseState: this.customerQuestions.addressQuestions.state
licenseState: this.customerQuestions.addressQuestions.state,
});
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'vinLookupResponse',
promise: vinLookupResponse
}
promise: vinLookupResponse,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
// If VIN Lookup by address is forbidden by State Restrictions then show an alert
if (!resultMap.vinLookupResponse.isStatePermissible) {
// State Restrictions forbid lookup by address
// State Restrictions forbid lookup by address
this.displayVinLookupByHomeAddressNotAllowedAlert = true;
this.$refs.siteFooter.disableForwardButton();
return this.$refs.siteFooter.removeLoader();
@ -250,12 +266,16 @@ export default {
// Handle cases for different amounts of VINS found for the address.
if (carsFound.length === 1) {
// Single VIN found
// Single VIN found
const carFound = carsFound[0].vehicle;
this.isCarIdDifferent = carFound.carId !== useMainStore().order.vehicle.carId;
if (this.isCarIdDifferent && carFound.carId !== this.previouslyEnteredCarId) {
// Display Alert
this.isCarIdDifferent =
carFound.carId !== useMainStore().order.vehicle.carId;
if (
this.isCarIdDifferent &&
carFound.carId !== this.previouslyEnteredCarId
) {
// Display Alert
this.previouslyEnteredCarId = carFound.carId;
this.customAlertData.vehicleInfo = carFound;
if (this.isTwoIdenticalYMMVehicleFound) {
@ -265,28 +285,39 @@ export default {
this.displayMatchedDifferentVehicleAlert = true;
}
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(carFound.carId);
this.isSelectedGlassAvailableForVehicle =
await isGlassAvailableForCarId(carFound.carId);
// Update button "Continue with..."
this.$refs.siteFooter
// eslint-disable-next-line max-len
.updateButtonText(`Continue with ${carFound.year} ${carFound.make} ${carFound.model} ${this.forwardButtonCarStyle}`);
.updateButtonText(
`Continue with ${carFound.year} ${carFound.make} ${carFound.model} ${this.forwardButtonCarStyle}`
);
return this.$refs.siteFooter.removeLoader();
}
// update data
vehicleInfoToCommit = Object.assign(carFound, { vin: carsFound[0].vin });
vehicleInfoToCommit = Object.assign(carFound, {
vin: carsFound[0].vin,
});
} else if (carsFound.length > 1) {
// If multiple cars were found and one and only one of them matches the carId entered, save the vehicle info
const matchingCars = carsFound.filter((vin) => vin.vehicle.carId === useMainStore().order.vehicle.carId);
const matchingCars = carsFound.filter(
(vin) =>
vin.vehicle.carId === useMainStore().order.vehicle.carId
);
if (matchingCars.length === 1) {
vehicleInfoToCommit = Object.assign(matchingCars[0].vehicle, {
vin: matchingCars[0].vin
});
vehicleInfoToCommit = Object.assign(
matchingCars[0].vehicle,
{
vin: matchingCars[0].vin,
}
);
}
} else {
// No VINS found.
// No VINS found.
this.displayVinNotFoundAlert = true;
this.$refs.siteFooter.disableForwardButton();
return this.$refs.siteFooter.removeLoader();
@ -295,19 +326,23 @@ export default {
// Save vehicle, customer, service and registration information
await useMainStore().saveRegistrationAddressLookup(
{
isSelectedGlassAvailableForVehicle: this.isSelectedGlassAvailableForVehicle,
isSelectedGlassAvailableForVehicle:
this.isSelectedGlassAvailableForVehicle,
vehicleInfo:
Object.keys(vehicleInfoToCommit).length === 0
? null
: vehicleInfoToCommit,
Object.keys(vehicleInfoToCommit).length === 0
? null
: vehicleInfoToCommit,
registrationInfo: {
firstName: this.customerQuestions.firstName,
lastName: this.customerQuestions.lastName,
address: this.customerQuestions.addressQuestions.streetAddress,
address:
this.customerQuestions.addressQuestions
.streetAddress,
city: this.customerQuestions.addressQuestions.city,
state: this.customerQuestions.addressQuestions.state,
zipCode: this.customerQuestions.addressQuestions.zipCode
}
zipCode:
this.customerQuestions.addressQuestions.zipCode,
},
},
false
);
@ -315,15 +350,18 @@ export default {
return this.navigateForward(carsFound);
},
async navigateForward(carsFound) {
// Match vehicles found to vehicles in state.
const matchingCars = carsFound.filter((car) => car.vehicle.carId === useMainStore().order.vehicle.carId);
// Match vehicles found to vehicles in state.
const matchingCars = carsFound.filter(
(car) =>
car.vehicle.carId === useMainStore().order.vehicle.carId
);
// If a different vehicle is found than the one entered and the selected glass
// is not available for that vehicle then navigate back to "vehicle-damage"
// display vehicle changed alert on that page.
if (
this.isCarIdDifferent
&& !this.isSelectedGlassAvailableForVehicle
this.isCarIdDifferent &&
!this.isSelectedGlassAvailableForVehicle
) {
this.$router.navigate(
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
@ -335,7 +373,8 @@ export default {
await this.navigateForwardWithSingleCarMatch();
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
this.navigationScenarios
.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
this.$route,
{},
{},
@ -348,7 +387,7 @@ export default {
this.displayMatchedDifferentVehicleAlert = false;
this.displayVinLookupByHomeAddressNotAllowedAlert = false;
this.$refs.siteFooter.enableForwardAction();
}
}
},
},
};
</script>

View file

@ -1,66 +1,84 @@
<template>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="fade-on-route-transition sub-container overflow-scroll px-5">
<vehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<alert
id="multiple-vehicles-alert"
ref="alertFoundMultipleVehicles"
cmsWidgetName="FoundMultipleVehicles"
class="my-5"
alertClass="alert-warning"
:manualHeadline="AlertFoundMultipleVehiclesHeader"
manualCopy=""
:isDismissible="false" />
<addressVehiclesQuestion
ref="addressVehiclesQuestion"
v-model="selectedVehicleVin"
cmsWidgetName="VehicleConfirmationQuestion"
:vehicles="VehiclesForQuestions"
:vehicleSelected="VehicleSelected"
validationRules="vehicle-required"
:isCarIdDifferent="isCarIdDifferent"
:displayMatchedDifferentVehicleAlert="displayMatchedDifferentVehicleAlert"
:displayMatchedTwoIdenticalYMMVehicleAlert="displayMatchedTwoIdenticalYMMVehicleAlert" />
<div
v-if="splitAlertProvideVinBodyForLink.length"
class="alert-provide-vin my-3">
<span
v-for="copy in splitAlertProvideVinBodyForLink"
:key="copy">
<span
v-if="doesCopyContainRouterLink(copy)"
class="text-body">
<router-link
:to="{
query: { issPage: `${getRouterLinkRouteFromCopy(copy)}` },
name: 'root',
}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link>
</span>
<span
v-else
class="m-0 text-body"
v-html="copy"></span>
</span>
</div>
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</Form>
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<alert
id="multiple-vehicles-alert"
ref="alertFoundMultipleVehicles"
cmsWidgetName="FoundMultipleVehicles"
class="my-5"
alertClass="alert-warning"
:manualHeadline="AlertFoundMultipleVehiclesHeader"
manualCopy=""
:isDismissible="false" />
<addressVehiclesQuestion
ref="addressVehiclesQuestion"
v-model="selectedVehicleVin"
cmsWidgetName="VehicleConfirmationQuestion"
:vehicles="VehiclesForQuestions"
:vehicleSelected="VehicleSelected"
validationRules="vehicle-required"
:isCarIdDifferent="isCarIdDifferent"
:displayMatchedDifferentVehicleAlert="
displayMatchedDifferentVehicleAlert
"
:displayMatchedTwoIdenticalYMMVehicleAlert="
displayMatchedTwoIdenticalYMMVehicleAlert
" />
<div
v-if="splitAlertProvideVinBodyForLink.length"
class="alert-provide-vin my-3">
<span
v-for="copy in splitAlertProvideVinBodyForLink"
:key="copy">
<span
v-if="doesCopyContainRouterLink(copy)"
class="text-body">
<router-link
:to="{
query: {
issPage: `${getRouterLinkRouteFromCopy(
copy
)}`,
},
name: 'root',
}"
>{{
getRouterLinkDisplayTextFromCopy(copy)
}}</router-link
>
</span>
<span
v-else
class="m-0 text-body"
v-html="copy"></span>
</span>
</div>
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</Form>
</template>
<script>
@ -77,7 +95,7 @@ import {
doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy
getRouterLinkDisplayTextFromCopy,
} from '@/helpers/cms-content-helper.js';
import routerParams from '@/router/router-constants/router-params';
import vinPagesMixin from '@/mixins/vin-pages-mixin';
@ -104,7 +122,7 @@ export default {
Form,
vehicleBanner,
alert,
addressVehiclesQuestion
addressVehiclesQuestion,
},
mixins: [baseFormMixin, vinPagesMixin],
async beforeRouteEnter(to, from, next) {
@ -114,8 +132,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -125,7 +143,7 @@ export default {
});
},
props: {
validationRules: String
validationRules: String,
},
setup() {
const mainStore = useMainStore();
@ -140,7 +158,7 @@ export default {
displayMatchedDifferentVehicleAlert: false,
displayMatchedTwoIdenticalYMMVehicleAlert: false,
previouslyEnteredCarId: '',
forwardButtonCarStyle: ''
forwardButtonCarStyle: '',
};
},
computed: {
@ -154,11 +172,9 @@ export default {
).replaceAll('{custom:vehicleCount}', this.vehicleCount);
},
isTwoIdenticalYMMVehicleFound() {
const vinYmmFound =
`${this.selectedVehicle?.vehicle.year} ${this.selectedVehicle?.vehicle.make} ${this.selectedVehicle?.vehicle.model}`;
const vinYmmExpected =
`${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return (vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase());
const vinYmmFound = `${this.selectedVehicle?.vehicle.year} ${this.selectedVehicle?.vehicle.make} ${this.selectedVehicle?.vehicle.model}`;
const vinYmmExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase();
},
AlertProvideVinBody() {
return this.getCmsContent('ProvideVinAlert', 'BodyText');
@ -178,7 +194,7 @@ export default {
vehicle: v.vehicle,
Text: `${v.vehicle.year} ${v.vehicle.make} ${v.vehicle.model}`,
Name: v.vin,
SubText: `VIN ${vinStart}${vinEnd}`
SubText: `VIN ${vinStart}${vinEnd}`,
};
});
return mappedData;
@ -187,11 +203,13 @@ export default {
return useMainStore().pageData(issPageValues.ADDRESS_VEHICLES);
},
selectedVehicle() {
return this.VehiclesForQuestions.find(({ vin }) => vin === this.selectedVehicleVin);
return this.VehiclesForQuestions.find(
({ vin }) => vin === this.selectedVehicleVin
);
},
VehicleSelected() {
return this.mainStore.order.vehicle;
}
},
},
watch: {
selectedVehicleVin: {
@ -199,10 +217,15 @@ export default {
this.resetWarningsAndErrors();
// does this vehicle match the previously selected carId?
this.isCarIdDifferent =
this.selectedVehicle?.vehicle.carId !== useMainStore().vehicle.carId;
if (this.isCarIdDifferent
&& this.selectedVehicle?.vehicle.carId !== this.previouslyEnteredCarId) {
this.previouslyEnteredCarId = this.selectedVehicle?.vehicle.carId;
this.selectedVehicle?.vehicle.carId !==
useMainStore().vehicle.carId;
if (
this.isCarIdDifferent &&
this.selectedVehicle?.vehicle.carId !==
this.previouslyEnteredCarId
) {
this.previouslyEnteredCarId =
this.selectedVehicle?.vehicle.carId;
if (this.isTwoIdenticalYMMVehicleFound) {
const carStyle = this.selectedVehicle?.vehicle.style;
this.displayMatchedTwoIdenticalYMMVehicleAlert = true;
@ -210,15 +233,21 @@ export default {
} else {
this.displayMatchedDifferentVehicleAlert = true;
}
this.$refs.siteFooter
.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} `
+ `${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model} ${this.forwardButtonCarStyle}`);
this.$refs.siteFooter.updateButtonText(
`Continue with ${this.selectedVehicle.vehicle.year} ` +
`${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model} ${this.forwardButtonCarStyle}`
);
} else {
this.$refs.siteFooter.updateButtonText(this.getCmsContent('SiteFooterWidget', 'ForwardButtonText'));
this.$refs.siteFooter.updateButtonText(
this.getCmsContent(
'SiteFooterWidget',
'ForwardButtonText'
)
);
}
},
deep: true
}
deep: true,
},
},
methods: {
doesCopyContainRouterLink,
@ -240,14 +269,15 @@ export default {
if (!vinLookup) {
return;
}
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(vinLookup.data.carId);
this.isSelectedGlassAvailableForVehicle =
await isGlassAvailableForCarId(vinLookup.data.carId);
await useMainStore().saveVin(
{
vehicleInfo: Object.assign(this.selectedVehicle.vehicle, {
vin: this.selectedVehicle.vin
vin: this.selectedVehicle.vin,
}),
isSelectedGlassAvailableForVehicle:
this.isSelectedGlassAvailableForVehicle
this.isSelectedGlassAvailableForVehicle,
},
false
);
@ -257,7 +287,10 @@ export default {
async navigateForward() {
// If the vehicle selected on this page is different from the one originally entered and the selected glass is not available
// for that vehicle, then navigate back to "vehicle-damage" and display vehicle changed alert on that page.
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
if (
this.isCarIdDifferent &&
!this.isSelectedGlassAvailableForVehicle
) {
this.$router.navigate(
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route,
@ -272,28 +305,28 @@ export default {
this.displayMatchedDifferentVehicleAlert = false;
this.displayMatchedTwoIdenticalYMMVehicleAlert = false;
this.forwardButtonCarStyle = '';
}
}
},
},
};
</script>
<style lang="scss" scoped>
.alert-provide-vin {
font-size: 0.875rem;
line-height: 1.4;
a {
//Per Devyn. This can't be documented in Figma so there is a comment with the Prototype mocks on the Quote page in Figma
text-underline-offset: 4px;
line-height: inherit;
}
font-size: 0.875rem;
line-height: 1.4;
a {
//Per Devyn. This can't be documented in Figma so there is a comment with the Prototype mocks on the Quote page in Figma
text-underline-offset: 4px;
line-height: inherit;
}
}
#multiple-vehicles-alert p {
margin-bottom: 0 !important; // Overrides extra margin-bottom on alert body text
margin-bottom: 0 !important; // Overrides extra margin-bottom on alert body text
}
.overflow-scroll {
height: calc(100% - 180px);
overflow-X: hidden !important;
overflow-x: hidden !important;
}
</style>

View file

@ -4,60 +4,66 @@
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<siteHeader
ref="siteHeader"
cmsWidgetName="SiteHeaderWidget" />
<div class="main-content-container">
<siteSubHeader
ref="siteSubHeader"
:cmsWidgetName="subHeaderCmsWidgetName"
:contentProperty="subHeaderContentProperty"
:stripRteStyle="stripRteStyle"
:subContentProperty="subContentProperty"
class="sub-header-content"
justification="left" />
<textboxQuestion
ref="firstName"
v-model="bailoutPageModel.firstName"
inputId="firstNameField"
cmsWidgetName="FirstNameQuestion"
isRequired
disableAutoFill
:validationRules="rules.firstName" />
<textboxQuestion
ref="lastName"
v-model="bailoutPageModel.lastName"
inputId="lastNameField"
cmsWidgetName="LastNameQuestion"
isRequired
disableAutoFill
:validationRules="rules.lastName" />
<textboxQuestion
ref="phoneNumber"
v-model="bailoutPageModel.phoneNumber"
inputId="phoneNumberField"
cmsWidgetName="PhoneNumberQuestion"
isRequired
:mask="phoneMask"
disableAutoFill
:validationRules="rules.phoneNumber" />
<textboxQuestion
ref="emailAddress"
v-model="bailoutPageModel.email"
inputId="emailAddressField"
cmsWidgetName="EmailAddressQuestion"
isRequired
disableAutoFill
:validationRules="rules.email" />
<siteFooter
ref="siteFooter"
class="footer-content-container"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
:isBackButtonHidden="!canNavigateBack"
@backClicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
<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="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
ref="siteSubHeader"
:cmsWidgetName="subHeaderCmsWidgetName"
:contentProperty="subHeaderContentProperty"
:stripRteStyle="stripRteStyle"
:subContentProperty="subContentProperty"
class="sub-header-content"
justification="left" />
<textboxQuestion
ref="firstName"
v-model="bailoutPageModel.firstName"
inputId="firstNameField"
cmsWidgetName="FirstNameQuestion"
isRequired
disableAutoFill
:validationRules="rules.firstName" />
<textboxQuestion
ref="lastName"
v-model="bailoutPageModel.lastName"
inputId="lastNameField"
cmsWidgetName="LastNameQuestion"
isRequired
disableAutoFill
:validationRules="rules.lastName" />
<textboxQuestion
ref="phoneNumber"
v-model="bailoutPageModel.phoneNumber"
inputId="phoneNumberField"
cmsWidgetName="PhoneNumberQuestion"
isRequired
:mask="phoneMask"
disableAutoFill
:validationRules="rules.phoneNumber" />
<textboxQuestion
ref="emailAddress"
v-model="bailoutPageModel.email"
inputId="emailAddressField"
cmsWidgetName="EmailAddressQuestion"
isRequired
disableAutoFill
:validationRules="rules.email" />
<siteFooter
ref="siteFooter"
class="footer-content-container"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
:isBackButtonHidden="!canNavigateBack"
@backClicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</Form>
@ -90,7 +96,7 @@ export default {
siteFooter,
textboxQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -100,8 +106,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
@ -121,14 +127,14 @@ export default {
widget: {
defaultSiteHeader: 'SiteSubHeaderWidget',
noTpa: 'ContentGroupNoTPAWidget',
notSeeingPreferredShop: 'ContentGroupNotSeeingPreferredShop'
notSeeingPreferredShop: 'ContentGroupNotSeeingPreferredShop',
},
rules: {
firstName: globalRules.FIRST_NAME_REQUIRED,
lastName: globalRules.LAST_NAME_REQUIRED,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
email: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`
}
email: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
},
};
},
computed: {
@ -179,13 +185,16 @@ export default {
},
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
}
},
},
methods: {
backButtonAction() {
// route to move backwards
this.mainStore.resetBailout();
this.$router.navigate(this.navigationScenarios.CLICKED_BACK_PREVIOUS, this.$route);
this.$router.navigate(
this.navigationScenarios.CLICKED_BACK_PREVIOUS,
this.$route
);
},
forwardButtonAction() {
this.mainStore.setBailoutContactInfo(this.bailoutPageModel);
@ -201,10 +210,10 @@ export default {
firstName: useMainStore().order.customer.firstName,
lastName: useMainStore().order.customer.lastName,
phoneNumber: useMainStore().order.customer.phoneNumber,
email: useMainStore().order.customer.emailAddress
email: useMainStore().order.customer.emailAddress,
};
}
}
},
},
};
</script>

View file

@ -1,22 +1,22 @@
<template>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<questionsPageLayout
ref="questionsPageLayout"
v-model="selectedAnswers"
isRequired
:isMetaValid="meta.valid"
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData"
:validationRules="rules.optionRequired"
:index="currentGlassIndex"
@forwardButtonAction="forwardButtonAction"
@backClick="navigateBackByVehicleQuestions" />
</Form>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<questionsPageLayout
ref="questionsPageLayout"
v-model="selectedAnswers"
isRequired
:isMetaValid="meta.valid"
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData"
:validationRules="rules.optionRequired"
:index="currentGlassIndex"
@forwardButtonAction="forwardButtonAction"
@backClick="navigateBackByVehicleQuestions" />
</Form>
</template>
<script>
// Import Supporting Files
@ -37,7 +37,7 @@ export default {
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
questionsPageLayout
questionsPageLayout,
},
mixins: [baseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
@ -47,8 +47,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -63,36 +63,49 @@ export default {
selectedAnswers: {},
currentGlassIndex: 0,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
optionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
AlertFewMoreQuestionsHeader() {
return this.getCmsContent('AdditionalPartsQuestionsAlert', 'HeadlineText');
return this.getCmsContent(
'AdditionalPartsQuestionsAlert',
'HeadlineText'
);
},
AlertFewMoreQuestionsCopy() {
return this.getCmsContent('AdditionalPartsQuestionsAlert', 'BodyText');
return this.getCmsContent(
'AdditionalPartsQuestionsAlert',
'BodyText'
);
},
partsOrQuestionsData() {
return useMainStore().pageData(issPageValues.CAPABILITY_QUESTIONS)
.partsOrQuestions;
}
},
},
mounted() {
this.getInitialQuestionData();
},
methods: {
arePagePrerequisitesValid() {
const capabilityQuestionsFromPageData = useMainStore().pageData(issPageValues.CAPABILITY_QUESTIONS);
const capabilityQuestionsFromPageData = useMainStore().pageData(
issPageValues.CAPABILITY_QUESTIONS
);
return (
capabilityQuestionsFromPageData?.partsOrQuestions?.some((part) => part?.glassName)
&& capabilityQuestionsFromPageData?.partsOrQuestions?.some((part) => part?.capabilityQuestions?.length > 0)
capabilityQuestionsFromPageData?.partsOrQuestions?.some(
(part) => part?.glassName
) &&
capabilityQuestionsFromPageData?.partsOrQuestions?.some(
(part) => part?.capabilityQuestions?.length > 0
)
);
},
getInitialQuestionData() {
// get any questions that were already answered
const alreadyAnsweredQuestions = useMainStore().damage.capabilityQuestionAnswers;
const alreadyAnsweredQuestions =
useMainStore().damage.capabilityQuestionAnswers;
this.questionsData = this.partsOrQuestionsData
.filter((x) => x.capabilityQuestions)
.map((glass, index) => {
@ -111,7 +124,10 @@ export default {
`selectedAnswers.${glass.answerKey}`,
(newValue) => {
if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey);
this.handleAnswerUpdates(
newValue,
glass.answerKey
);
}
},
{ deep: true }
@ -124,7 +140,9 @@ export default {
// get answerResult2 of returned answer
let selectedAnswerResult2;
glass.questions.forEach((q) => {
const idx = q.answers.findIndex((a) => a.answerResult === glass.answerData.answerResult);
const idx = q.answers.findIndex(
(a) => a.answerResult === glass.answerData.answerResult
);
if (idx !== -1) {
selectedAnswerResult2 = q.answers[idx].answerResult2;
}
@ -136,7 +154,7 @@ export default {
result1: glass.answerData.answerResult,
result2: selectedAnswerResult2,
answeredQuestions: glass.answerData.answeredQuestions,
isSuppressedPart: glass.isSuppressedPart
isSuppressedPart: glass.isSuppressedPart,
};
});
// clear out answerData for future page loads; must occur prior to store save
@ -144,22 +162,25 @@ export default {
glass.answerData = {};
});
// save to store as order.damage.moldingQuestionArrays (array)
await this.mainStore.saveCapabilityQuestionAnswers(questionAnswersArray);
await this.mainStore.saveCapabilityQuestionAnswers(
questionAnswersArray
);
// get parts from the capabilityQuestionAnswers
const partsOrQuestions = this.partsOrQuestionsData;
// eslint-disable-next-line no-restricted-syntax
for (const answer of questionAnswersArray) {
partsOrQuestions.find((partOrQuestion) => (
partOrQuestion.glassLocation === answer.glassLocation
&& partOrQuestion.glassName === answer.glassName
)).parts[0].childParts = [
partsOrQuestions.find(
(partOrQuestion) =>
partOrQuestion.glassLocation === answer.glassLocation &&
partOrQuestion.glassName === answer.glassName
).parts[0].childParts = [
{
partNumber: answer.partNum
}
partNumber: answer.partNum,
},
];
}
this.navigateForward(partsOrQuestions, null);
}
}
},
},
};
</script>

View file

@ -1,11 +1,17 @@
<template>
<div class="page-container-grouped-styles">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="main-content-container">
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="sub-header-content"
justification="left" />
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="sub-header-content"
justification="left" />
</div>
</div>
</div>
</template>
@ -23,7 +29,7 @@ export default {
name: 'contact-confirmation',
components: {
siteHeader,
siteSubHeader
siteSubHeader,
},
async beforeRouteEnter(to, from, next) {
// Call APIs
@ -32,8 +38,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
@ -47,13 +53,15 @@ export default {
return { mainStore };
},
data() {
const contactConfirmationData = useMainStore().pageData(issPageValues.CONTACT_CONFIRMATION);
const contactConfirmationData = useMainStore().pageData(
issPageValues.CONTACT_CONFIRMATION
);
return {
contactConfirmationModel: contactConfirmationData
contactConfirmationModel: contactConfirmationData,
};
},
computed: {},
methods: {}
methods: {},
};
</script>

View file

@ -4,12 +4,16 @@
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">
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
id="sub-header"
ref="siteSubHeader"
@ -66,10 +70,9 @@
:cmsWidgetName="widget.notesQuestion"
maxLength="250"
:inputRows="4" />
<p
ref="disclaimerText"
class="caption dark-gray mt-6">
{{ textUpdateDisclaimerText }} I also agree to Safelite's
<p ref="disclaimerText" class="caption dark-gray mt-6">
{{ textUpdateDisclaimerText }} I also agree to
Safelite's
<textLink
ref="privacyPolicyLink"
class="normal-line-height"
@ -125,7 +128,7 @@ export default {
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
textLink
textLink,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -136,12 +139,14 @@ export default {
});
},
data() {
const { firstName,
const {
firstName,
lastName,
emailAddress,
servicePhone,
requestTextUpdates,
notesForTechnician } = useMainStore().contactInfo;
notesForTechnician,
} = useMainStore().contactInfo;
return {
firstName,
lastName,
@ -159,14 +164,14 @@ export default {
requestTextUpdates: 'TextContentWidget',
notesQuestion: 'NotesQuestionWidget',
disclaimer: 'TextUpdateDisclaimerWidget',
siteFooter: 'SiteFooterWidget'
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}`
}
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
},
};
},
computed: {
@ -174,7 +179,10 @@ export default {
* @returns {string} Returns the CMS text associated with the "get text updates" checkbox.
*/
requestTextUpdatesCheckboxText() {
return `${this.getCmsContent(this.widget.requestTextUpdates, 'Text')}*`;
return `${this.getCmsContent(
this.widget.requestTextUpdates,
'Text'
)}*`;
},
/**
* @returns {string} Returns the CMS text associated with the "get text updates" checkbox.
@ -184,10 +192,9 @@ export default {
},
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
}
},
},
methods:
{
methods: {
/**
* @summary Steps to perform when forward button clicked.
*/
@ -197,28 +204,32 @@ export default {
lastName: this.lastName,
emailAddress: this.emailAddress,
requestTextUpdates: this.requestTextUpdates,
notesForTechnician: this.notesForTechnician
notesForTechnician: this.notesForTechnician,
};
useMainStore().updateContactInfo(contactInfo);
if (this.requestTextUpdates) {
useMainStore().updatePhoneNumbers({
service: this.phoneNumber,
alternative: this.phoneNumber
alternative: this.phoneNumber,
});
} else {
useMainStore().updatePhoneNumbers({
home: this.phoneNumber,
service: this.phoneNumber
service: this.phoneNumber,
});
}
const scenario = useMainStore().order.serviceLocation.IsSafeliteProvider === false
? this.navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP
: this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP;
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>
@ -230,5 +241,4 @@ export default {
.normal-line-height {
line-height: normal;
}
</style>

View file

@ -1,111 +1,99 @@
<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">
<loadingModal
ref="loadingModal"
:textSlides="loadingText" />
<siteHeader
ref="siteHeader"
cmsWidgetName="SiteHeaderWidget" />
<div class="select-car">
<div class="container-fluid pb-2">
<div class="row px-3">
<div class="col">
<div class="pb-1 mt-4">
<h5
ref="siteSubHeader"
class="text-center text-black"
v-html="coverageStatementSubHeader">
</h5>
<div
ref="explanatoryText"
class="body-text text-center mt-2"
v-html="explanatoryText">
</div>
<div
ref="secondaryText"
class="text-center mt-4 mb-1 fw-bold text-black"
v-html="secondaryText">
</div>
<div
v-if="isDeductibleVisible"
class="d-flex justify-content-center cost">
{{ formatAmountInDollars(deductibleValue) }}
</div>
<div
v-if="isQuoteDisplayed"
class="d-flex justify-content-center cost mb-0">
{{ formatAmountInDollars(totalServicePrice) }}
</div>
<div
v-if="isITACQuoteVisible"
class="d-flex justify-content-center mb-4 deductible-text">
{{ deductibleText }}&nbsp;
<span class="text-success fw-bold">{{ formatAmountInDollars(deductibleValue) }}</span>
</div>
<alert
v-if="isITACQuoteVisible"
ref="verifiedITACAlert"
class="mb-5"
cmsWidgetName="VerifiedITACAlert"
:manualHeadline="verifiedItacAlertHeader"
:manualCopy="verifiedItacAlertBody"
alertClass="alert-success"
:isDismissible="false">
</alert>
<div
class="fw-bold text-black mt-5 mb-2"
v-html="nextStepsHeader">
</div>
<div
class="body-text"
v-html="nextStepsBody">
</div>
<buttonQuestion
v-if="isQuoteDisplayed"
v-model="selectedProvider"
cmsWidgetName="ServiceProviderQuestion"
:questionText="serviceProviderQuestionText"
:answers="serviceProviderQuestionAnswers"
groupName="ServiceProviderQuestionOption"
buttonTypeString="listButton"
isRequired
:validationRules="rules.selectionRequired">
</buttonQuestion>
<text-block
v-if="isQuoteDisplayed"
cmsWidgetName="DisclaimerWidget"
typeStyle="caption" />
</div>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBackByVehicleQuestions"
@forwardClicked="navigateForward" />
</div>
</div>
</div>
</div>
</div>
</div>
<recalModal
ref="RecalModal"
cmsWidgetName="RecalModal" />
<contentGroupModal
ref="DeductibleModal"
cmsWidgetName="DeductibleModal"
class="deductible-modal" />
</Form>
<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">
<loadingModal
ref="loadingModal"
:textSlides="loadingText" />
<siteHeader
ref="siteHeader"
cmsWidgetName="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center pt-5">
<div class="col-md-6 col-xl-4">
<h5
ref="siteSubHeader"
class="text-center text-black"
v-html="coverageStatementSubHeader"></h5>
<div
ref="explanatoryText"
class="body-text text-center mt-2"
v-html="explanatoryText"></div>
<div
ref="secondaryText"
class="text-center mt-4 mb-1 fw-bold text-black"
v-html="secondaryText"></div>
<div
v-if="verifiedDeductible"
class="d-flex justify-content-center cost">
{{ deductibleForDisplay }}
</div>
<div
v-if="isQuoteDisplayed"
class="d-flex justify-content-center cost mb-0">
{{ servicePriceForDisplay }}
</div>
<div
v-if="verifiedITAC"
class="d-flex justify-content-center mb-4 deductible-text">
{{ deductibleText }}&nbsp;
<span class="text-success fw-bold">{{
deductibleForDisplay
}}</span>
</div>
<alert
v-if="verifiedITAC"
ref="verifiedITACAlert"
class="mb-5"
cmsWidgetName="VerifiedITACAlert"
:manualHeadline="verifiedItacAlertHeader"
:manualCopy="verifiedItacAlertBody"
alertClass="alert-success"
:isDismissible="false">
</alert>
<div
class="fw-bold text-black mt-5 mb-2"
v-html="nextStepsHeader"></div>
<div class="body-text" v-html="nextStepsBody"></div>
<buttonQuestion
v-if="isQuoteDisplayed"
v-model="selectedProvider"
cmsWidgetName="ServiceProviderQuestion"
:questionText="serviceProviderQuestionText"
:answers="serviceProviderQuestionAnswers"
groupName="ServiceProviderQuestionOption"
buttonTypeString="listButton"
isRequired
:validationRules="rules.selectionRequired">
</buttonQuestion>
<text-block
v-if="isQuoteDisplayed"
cmsWidgetName="DisclaimerWidget"
typeStyle="caption" />
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBackByVehicleQuestions"
@forwardClicked="navigateForward" />
</div>
</div>
</div>
<recalModal ref="RecalModal" cmsWidgetName="RecalModal" />
<contentGroupModal
ref="DeductibleModal"
cmsWidgetName="DeductibleModal"
class="deductible-modal" />
</Form>
</template>
<script>
// Import Component
import { Form } from 'vee-validate';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
@ -119,7 +107,11 @@ import textBlock from '@/digital-components/text-block/text-block.vue';
import pageVariations from '@/constants/coverage-statement-page-variations';
// Import Supporting Files
import { fetchCmsContentForPage, setupModalLinks, processIfStatements } from '@/helpers/cms-content-helper.js';
import {
fetchCmsContentForPage,
setupModalLinks,
processIfStatements,
} from '@/helpers/cms-content-helper.js';
import settleAllPromises from '@/helpers/layout-helper.js';
import { getDamageString } from '@/helpers/damage-helper.js';
import { useMainStore } from '@/store/index.js';
@ -148,24 +140,25 @@ export default {
contentGroupModal,
buttonQuestion,
loadingModal,
textBlock
textBlock,
},
mixins: [baseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to?.query?.issPage);
const supportingItemsPromise = await useMainStore().getSupportingItems();
const supportingItemsPromise =
await useMainStore().getSupportingItems();
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'supportingItems',
promise: supportingItemsPromise
}
promise: supportingItemsPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -175,7 +168,7 @@ export default {
// TODO SSR-1165: Recycle fee needs removed from quote calculation
const availableLineItems = [
...(resultMap.supportingItems ?? []),
...(clonedGlassParts ?? [])
...(clonedGlassParts ?? []),
];
let hasBailedOut = false;
@ -183,12 +176,19 @@ export default {
const { policy, vehicle } = useMainStore();
if (policy.policyLookupSuccessful && vehicle.policyVehicleId >= 0) {
await useMainStore().getFinalDeductible();
pricingResults = await useMainStore().getPriceOrderItems(availableLineItems)
pricingResults = await useMainStore()
.getPriceOrderItems(availableLineItems)
.catch((err) => {
useMainStore().setBailout(bailoutMessage.pricingResponseError(
availableLineItems.map((li) => li.partNumber),
{ code: err.code, message: err.message, data: err.data }
));
useMainStore().setBailout(
bailoutMessage.pricingResponseError(
availableLineItems.map((li) => li.partNumber),
{
code: err.code,
message: err.message,
data: err.data,
}
)
);
hasBailedOut = true;
next(`/?issPage=${issPageValues.BAILOUT_PAGE}`);
});
@ -218,10 +218,10 @@ export default {
loadingText: [
'Connecting to your insurance company',
'Nearly there',
'Finishing up'
'Finishing up',
],
rules: {
selectionRequired: globalRules.OPTION_REQUIRED
selectionRequired: globalRules.OPTION_REQUIRED,
},
supportingItems: null,
widget: {
@ -229,8 +229,8 @@ export default {
verifiedItacAlert: 'VerifiedITACAlert',
explanatoryText: 'ExplanatoryTextWidget',
nextStep: 'NextStepsWidget',
serviceProviderQuestion: 'ServiceProviderQuestion'
}
serviceProviderQuestion: 'ServiceProviderQuestion',
},
};
},
computed: {
@ -240,9 +240,10 @@ export default {
issConfig,
policy,
payment,
isClaimRegistrationRequired
isClaimRegistrationRequired,
} = useMainStore();
const registerClaimSuccessful = payment.insuranceCoverage.isVerified;
const registerClaimSuccessful =
payment.insuranceCoverage.isVerified;
if (!policy.policyLookupSuccessful) {
return pageVariations.UNVERIFIED;
@ -287,12 +288,14 @@ export default {
);
},
verifiedItacAlertBody() {
const itacCostSavings = this.deductibleValue - this.totalServicePrice;
const itacCostSavings =
this.deductibleValue - this.totalServicePrice;
return this.getCmsContent(
this.widget.verifiedItacAlert,
widgetFields.ALERT_WIDGET.BODY_TEXT
)?.replaceAll(
'{custom:costSavings}',
formatAmountInDollars(itacCostSavings)
);
},
@ -336,12 +339,16 @@ export default {
isDeductibleVisible() {
return this.pageVariation === pageVariations.DEDUCTIBLE;
},
isUnverifiedVisible() {
return this.pageVariation === pageVariations.UNVERIFIED;
},
isADAS() {
const { glassParts } = useMainStore().order.lineItems;
return glassParts !== null && !!glassParts.find((part) => part.requiresRecalibration);
return (
glassParts !== null &&
!!glassParts.find((part) => part.requiresRecalibration)
);
},
totalServicePrice() {
return getPriceOfLineItems(this.baseServiceLineItems);
@ -366,20 +373,25 @@ export default {
policy,
vehicle,
isClaimRegistrationRequired,
isClaimAlreadyRegistered
isClaimAlreadyRegistered,
} = useMainStore();
const { policyVehicleId } = vehicle;
return policy.policyLookupSuccessful
&& policyVehicleId != null
&& policyVehicleId >= 0
&& isClaimRegistrationRequired
&& !isClaimAlreadyRegistered
&& !this.isNoCompQuoteVisible;
}
return (
policy.policyLookupSuccessful &&
policyVehicleId != null &&
policyVehicleId >= 0 &&
isClaimRegistrationRequired &&
!isClaimAlreadyRegistered &&
!this.isNoCompQuoteVisible
);
},
},
watch: {
selectedProvider() {
const buttonText = this.selectedProvider === SAFELITE_PROVIDER ? 'Continue with Safelite' : 'Safelite';
const buttonText =
this.selectedProvider === SAFELITE_PROVIDER
? 'Continue with Safelite'
: 'Safelite';
this.$refs.siteFooter.updateButtonText(buttonText);
},
nextStepsBody(newValue, oldValue) {
@ -387,7 +399,7 @@ export default {
setupModalLinks(this, 'RecalModal');
setupModalLinks(this, 'DeductibleModal');
}
}
},
},
mounted() {
setupModalLinks(this);
@ -399,12 +411,15 @@ export default {
async initializeComponent() {
useMainStore().updatePolicyITACFlag(this.isITACQuoteVisible);
// TODO how should coverage status be updated
const coverageStatus = this.isITACQuoteVisible || this.isNoCompQuoteVisible
? coverageStatuses.VERIFIED
: coverageStatuses.PENDING;
const coverageStatus =
this.isITACQuoteVisible || this.isNoCompQuoteVisible
? coverageStatuses.VERIFIED
: coverageStatuses.PENDING;
useMainStore().updateCoverageStatus(coverageStatus);
if (this.shouldRegisterClaim) {
await useMainStore().registerClaim()?.catch(() => {});
await useMainStore()
.registerClaim()
?.catch(() => {});
}
this.$refs.loadingModal.hideModal();
},
@ -413,17 +428,27 @@ export default {
useMainStore().updateSupportingItems(this.supportingItems);
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD);
} else if (this.isITACQuoteVisible || this.isNoCompQuoteVisible) {
useMainStore().updateIsSafeliteProvider(this.selectedProvider === SAFELITE_PROVIDER);
useMainStore().updateIsSafeliteProvider(
this.selectedProvider === SAFELITE_PROVIDER
);
if (this.selectedProvider === SAFELITE_PROVIDER) {
useMainStore().updateSupportingItems(this.supportingItems);
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE);
this.navigateWithScenario(
navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE
);
} else {
useMainStore().setBailout(bailoutMessage.RequestCallback());
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP);
this.navigateWithScenario(
navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP
);
}
} else {
useMainStore().setBailout(bailoutMessage.coverageStatementInvalidState());
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_INVALID_STATE);
useMainStore().setBailout(
bailoutMessage.coverageStatementInvalidState()
);
this.navigateWithScenario(
navigationScenarios.CLICKED_FORWARD_WITH_INVALID_STATE
);
}
},
navigateWithScenario(scenario) {
@ -431,7 +456,11 @@ export default {
},
getTextFromCmsWithCustomIfStatements(widgetName, widgetField) {
const rawText = this.getCmsContent(widgetName, widgetField);
return processIfStatements(rawText, 'custom', this.getCustomValueFromString);
return processIfStatements(
rawText,
'custom',
this.getCustomValueFromString
);
},
getCustomValueFromString(str) {
const { isRepair } = useMainStore().damage;
@ -451,9 +480,13 @@ export default {
case 'nonADASRepair':
return isRepair;
case 'deductibleOverZero':
return this.isDeductibleVisible && this.deductibleValue !== 0; // TODO what if deductible is negative?
return (
this.isDeductibleVisible && this.deductibleValue !== 0
); // TODO what if deductible is negative?
case 'isDeductibleZero':
return this.isDeductibleVisible && this.deductibleValue === 0;
return (
this.isDeductibleVisible && this.deductibleValue === 0
);
default:
return null;
}
@ -464,17 +497,17 @@ export default {
setBaseServiceLineItems(lineItems) {
this.baseServiceLineItems = lineItems;
},
formatAmountInDollars
}
formatAmountInDollars,
},
};
</script>
<style lang="scss" scoped>
.cost {
color: $green;
font-size: 2rem;
font-weight: $font-weight-light;
line-height: 2.75rem;
color: $green;
font-size: 2rem;
font-weight: $font-weight-light;
line-height: 2.75rem;
}
.deductible-text {
@ -501,23 +534,22 @@ export default {
}
:deep .deductible-modal {
p {
margin-bottom: 0 !important;
font-size: 1rem;
line-height: 1.625rem;
}
img.mb-4 {
margin: 0 !important;
}
h5 {
color: black;
}
p:last-child {
margin-top: 0.5rem;
}
p {
margin-bottom: 0 !important;
font-size: 1rem;
line-height: 1.625rem;
}
img.mb-4 {
margin: 0 !important;
}
h5 {
color: black;
}
p:last-child {
margin-top: 0.5rem;
}
}
.modal {
overflow: hidden;
}
</style>

View file

@ -4,12 +4,16 @@
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition">
<siteHeader
ref="siteHeader"
:cmsWidgetName="widget.siteHeader" />
<div class="container-fluid px-6">
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
id="sub-header"
ref="siteSubHeader"
@ -25,8 +29,7 @@
groupName="existingOrNewQuestionOption"
buttonTypeString="listButton"
isRequired
:validationRules="rules.selectionRequired">
</buttonQuestion>
:validationRules="rules.selectionRequired" />
<siteFooter
ref="siteFooter"
class="my-5"
@ -63,7 +66,7 @@ export default {
buttonQuestion,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -84,47 +87,62 @@ export default {
siteHeader: 'SiteHeaderWidget',
siteSubHeader: 'SiteSubHeaderWidget',
existingOrNewQuestion: 'ExistingOrNewQuestion',
siteFooter: 'SiteFooterWidget'
siteFooter: 'SiteFooterWidget',
},
rules: {
selectionRequired: globalRules.OPTION_REQUIRED
}
selectionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
questionText() {
return this.getCmsContent(this.widget.existingOrNewQuestion, 'QuestionText');
return this.getCmsContent(
this.widget.existingOrNewQuestion,
'QuestionText'
);
},
answersFromCms() {
return this.getCmsContent(this.widget.existingOrNewQuestion, 'Answers') ?? [];
return (
this.getCmsContent(
this.widget.existingOrNewQuestion,
'Answers'
) ?? []
);
},
getNewOrderSelectionName() {
return this.answersFromCms?.[0]?.Name ?? '';
},
duplicateOrders() {
const duplicateOrderText = 'Finish Existing Claim';
const orders = this.mainStore.applicationUser.duplicateOrders;
return orders?.map((o) => {
const vehicle = !!o.vehicleYear && !!o.vehicleMake && !!o.vehicleModel
? `${o.vehicleYear} ${o.vehicleMake} ${o.vehicleModel}`
: null;
const orders = useMainStore().applicationUser.duplicateOrders;
return (
orders?.map((o) => {
const vehicle =
!!o.vehicleYear && !!o.vehicleMake && !!o.vehicleModel
? `${o.vehicleYear} ${o.vehicleMake} ${o.vehicleModel}`
: null;
const dateOfLoss = o.responseDate == null ? '' : new Date(o.responseDate).toLocaleDateString();
const subtext = vehicle && o.responseDate
? `${vehicle}, ${dateOfLoss}`
: (vehicle ?? '').concat(dateOfLoss);
const dateOfLoss =
o.responseDate == null
? ''
: new Date(o.responseDate).toLocaleDateString();
const subtext =
vehicle && o.responseDate
? `${vehicle}, ${dateOfLoss}`
: (vehicle ?? '').concat(dateOfLoss);
return {
Text: duplicateOrderText,
Name: o.referralNumber,
SubText: toTitleCase(subtext),
value: o.correlationId
};
}) ?? [];
return {
Text: duplicateOrderText,
Name: o.referralNumber,
SubText: toTitleCase(subtext),
value: o.correlationId,
};
}) ?? []
);
},
answers() {
return [...this.duplicateOrders, ...this.answersFromCms];
}
},
},
methods: {
/**
@ -132,9 +150,13 @@ export default {
*/
async forwardButtonAction() {
if (this.selectedAnswer !== null) {
const selectedReferral = this.mainStore.applicationUser.duplicateOrders.find((o) => o.correlationId === this.selectedAnswer);
const selectedReferral =
this.mainStore.applicationUser.duplicateOrders.find(
(o) => o.correlationId === this.selectedAnswer
);
if (selectedReferral) {
await this.mainStore.loadSession(selectedReferral)
await this.mainStore
.loadSession(selectedReferral)
.catch(() => {})
.finally(() => {
this.navigateForward();
@ -142,7 +164,6 @@ export default {
return;
}
}
this.navigateForward();
},
navigateForward() {
@ -158,12 +179,14 @@ export default {
if (!this.mainStore.order.loadedFromDupeCheck) {
if (policyVehicles.length !== 0) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
this.navigationScenarios
.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
this.$route
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
this.navigationScenarios
.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
this.$route
);
}
@ -172,22 +195,25 @@ export default {
if (this.mainStore.order.vehicle.vin) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_POLICY_VEHICLE,
this.navigationScenarios
.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_POLICY_VEHICLE,
this.$route
);
} else if (policyVehicles.length !== 0) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NON_POLICY_VEHICLE,
this.navigationScenarios
.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NON_POLICY_VEHICLE,
this.$route
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NO_POLICY_VEHICLES,
this.navigationScenarios
.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NO_POLICY_VEHICLES,
this.$route
);
}
}
}
},
},
};
</script>
@ -209,9 +235,8 @@ export default {
margin-top: map-get($spacers, 4);
margin-bottom: map-get($spacers, 2);
}
.form-test-error{
.form-test-error {
margin-top: 0 !important;
}
}
</style>

View file

@ -4,72 +4,70 @@
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="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<alert
v-if="displayVinNotFoundAlert"
ref="alertVinNotFound"
class="mb-4"
cmsWidgetName="AlertVinNotFoundWidget"
alertClass="alert-danger"
:isDismissible="false" />
<alert
v-if="displayMatchedDifferentVehicleAlert"
ref="alertMatchedDifferentVehicle"
class="mb-4"
cmsWidgetName="AlertMatchedDifferentVehicleWidget"
:manualHeadline="AlertMatchedDifferentVehicleHeader"
:manualCopy="AlertMatchedDifferentVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<alert
v-if="displayMatchedTwoIdenticalYMMVehicleAlert"
ref="alertMatchedTwoIdenticalYMMVehicle"
class="mb-4"
cmsWidgetName="AlertMatchedTwoIdenticalYMMVehicleWidget"
:manualHeadline="AlertMatchedTwoIdenticalYMMVehicleHeader"
:manualCopy="AlertMatchedTwoIdenticalYMMVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<textboxQuestion
id="license-plate-question-wrapper"
v-model="licensePlate"
cmsWidgetName="LicensePlateNumberQuestionWidget"
isRequired
disableAutoFill
inputId="license-plate-question"
validationRules="license-plate-required" />
<dropdownQuestion
ref="state"
v-model="licenseState"
cmsWidgetName="StateQuestionWidget"
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="stateOptions"
disableAutoFill
validationRules="state-required"
class="mt-4 mb-2" />
<siteFooter
ref="siteFooter"
class="mt-5"
:isForwardActionDisabled="!meta.valid"
cmsWidgetName="SiteFooterWidget"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</div>
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<alert
v-if="displayVinNotFoundAlert"
ref="alertVinNotFound"
class="mb-4"
cmsWidgetName="AlertVinNotFoundWidget"
alertClass="alert-danger"
:isDismissible="false" />
<alert
v-if="displayMatchedDifferentVehicleAlert"
ref="alertMatchedDifferentVehicle"
class="mb-4"
cmsWidgetName="AlertMatchedDifferentVehicleWidget"
:manualHeadline="AlertMatchedDifferentVehicleHeader"
:manualCopy="AlertMatchedDifferentVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<alert
v-if="displayMatchedTwoIdenticalYMMVehicleAlert"
ref="alertMatchedTwoIdenticalYMMVehicle"
class="mb-4"
cmsWidgetName="AlertMatchedTwoIdenticalYMMVehicleWidget"
:manualHeadline="
AlertMatchedTwoIdenticalYMMVehicleHeader
"
:manualCopy="AlertMatchedTwoIdenticalYMMVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<textboxQuestion
id="license-plate-question-wrapper"
v-model="licensePlate"
cmsWidgetName="LicensePlateNumberQuestionWidget"
isRequired
disableAutoFill
inputId="license-plate-question"
validationRules="license-plate-required" />
<dropdownQuestion
ref="state"
v-model="licenseState"
cmsWidgetName="StateQuestionWidget"
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="stateOptions"
disableAutoFill
validationRules="state-required"
class="mt-4 mb-2" />
<siteFooter
ref="siteFooter"
class="mt-5"
:isForwardActionDisabled="!meta.valid"
cmsWidgetName="SiteFooterWidget"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
@ -83,7 +81,10 @@ import { useMainStore } from '@/store';
import errorMessages from '@/constants/error-messages';
import { required } from '@/helpers/validation-rules';
import { defineRule, Form } from 'vee-validate';
import { getDamageString, isGlassAvailableForCarId } from '@/helpers/damage-helper.js';
import {
getDamageString,
isGlassAvailableForCarId,
} from '@/helpers/damage-helper.js';
import routerParams from '@/router/router-constants/router-params.js';
import states from '@/constants/states';
@ -100,7 +101,10 @@ import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-qu
import alert from '@/ux-components/alert/alert.vue';
// Define Validation Rules
defineRule('license-plate-required', required(errorMessages.LICENSE_PLATE_REQUIRED));
defineRule(
'license-plate-required',
required(errorMessages.LICENSE_PLATE_REQUIRED)
);
defineRule('state-required', required(errorMessages.STATE_REQUIRED));
export default {
@ -114,7 +118,7 @@ export default {
vehicleBanner,
textboxQuestion,
dropdownQuestion,
alert
alert,
},
mixins: [baseFormMixin, vinPagesMixin],
async beforeRouteEnter(to, from, next) {
@ -125,8 +129,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -149,7 +153,7 @@ export default {
isCarIdDifferent: false,
customAlertData: {},
isSelectedGlassAvailableForVehicle: true,
forwardButtonCarStyle: ''
forwardButtonCarStyle: '',
};
},
computed: {
@ -163,10 +167,12 @@ export default {
const vinYmmFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected =
`${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
const vinYmmExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return this.getCmsContent('AlertMatchedDifferentVehicleWidget', 'BodyText')
return this.getCmsContent(
'AlertMatchedDifferentVehicleWidget',
'BodyText'
)
.replaceAll('{custom:damage}', getDamageString())
.replaceAll('{custom:vinYmmFound}', vinYmmFound)
.replaceAll('{custom:vinYmmExpected}', vinYmmExpected);
@ -185,7 +191,10 @@ export default {
// eslint-disable-next-line max-len
`${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model} ${this.mainStore.order.vehicle.style}`;
return this.getCmsContent('AlertMatchedTwoIdenticalYMMVehicleWidget', 'BodyText')
return this.getCmsContent(
'AlertMatchedTwoIdenticalYMMVehicleWidget',
'BodyText'
)
.replaceAll('{custom:damage}', getDamageString())
.replaceAll('{custom:vinYmmsFound}', vinYmmsFound)
.replaceAll('{custom:vinYmmsExpected}', vinYmmsExpected);
@ -194,23 +203,26 @@ export default {
const vinYmmFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected =
`${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return (vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase());
const vinYmmExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase();
},
stateOptions: {
get() {
return states;
}
}
},
},
},
watch: {
licensePlate() {
this.$refs.siteFooter.updateButtonText(this.getCmsContent('SiteFooterWidget', 'ForwardButtonText'));
this.$refs.siteFooter.updateButtonText(
this.getCmsContent('SiteFooterWidget', 'ForwardButtonText')
);
},
registrationZipCode() {
this.$refs.siteFooter.updateButtonText(this.getCmsContent('SiteFooterWidget', 'ForwardButtonText'));
}
this.$refs.siteFooter.updateButtonText(
this.getCmsContent('SiteFooterWidget', 'ForwardButtonText')
);
},
},
mounted() {
this.attachCustomEvents();
@ -221,7 +233,8 @@ export default {
return useMainStore().order.vehicle.carId !== null;
},
loadDefaultsFromStore() {
this.customerQuestions = this.mainStore.customerData.addressQuestions.state;
this.customerQuestions =
this.mainStore.customerData.addressQuestions.state;
},
attachCustomEvents() {
this.prependActionToMethod(this, this.forwardButtonAction, () => {
@ -238,14 +251,17 @@ export default {
this.resetWarningsAndErrors();
// Lookup VIN
const vinLookupResponse = await useMainStore().lookupVinByPlate(this.licensePlate, this.licenseState);
const vinLookupResponse = await useMainStore().lookupVinByPlate(
this.licensePlate,
this.licenseState
);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'vinLookupResponse',
promise: vinLookupResponse
}
promise: vinLookupResponse,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -265,8 +281,8 @@ export default {
vehicleFromLookup.carId !== useMainStore().order.vehicle.carId;
// Handle changing car
if (
this.isCarIdDifferent
&& vehicleFromLookup.carId !== this.previouslyEnteredCarId
this.isCarIdDifferent &&
vehicleFromLookup.carId !== this.previouslyEnteredCarId
) {
// Display Alert
this.previouslyEnteredCarId = vehicleFromLookup.carId;
@ -279,24 +295,30 @@ export default {
this.displayMatchedDifferentVehicleAlert = true;
}
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(vehicleFromLookup.carId);
this.isSelectedGlassAvailableForVehicle =
await isGlassAvailableForCarId(vehicleFromLookup.carId);
// Update button "Continue with..."
this.$refs.siteFooter
// eslint-disable-next-line max-len
.updateButtonText(`Continue with ${vehicleFromLookup.year} ${vehicleFromLookup.make} ${vehicleFromLookup.model} ${this.forwardButtonCarStyle}`);
.updateButtonText(
`Continue with ${vehicleFromLookup.year} ${vehicleFromLookup.make} ${vehicleFromLookup.model} ${this.forwardButtonCarStyle}`
);
return this.$refs.siteFooter.removeLoader();
}
// Save vehicle, license plate, and registration information
await useMainStore().saveRegistrationLicensePlateLookup(
{
isSelectedGlassAvailableForVehicle: this.isSelectedGlassAvailableForVehicle,
vehicleInfo: Object.assign(vinLookupResponse.data.vehicle, { vin: vinLookupResponse.data.vin }),
isSelectedGlassAvailableForVehicle:
this.isSelectedGlassAvailableForVehicle,
vehicleInfo: Object.assign(vinLookupResponse.data.vehicle, {
vin: vinLookupResponse.data.vin,
}),
registrationInfo: {
licensePlate: this.licensePlate,
state: this.licenseState
}
state: this.licenseState,
},
},
false
);
@ -307,7 +329,10 @@ export default {
// If a different vehicle is found than the one entered and the selected glass is
// not available for that vehicle then navigate back to "vehicle-damage"
// display vehicle changed alert on that page.
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
if (
this.isCarIdDifferent &&
!this.isSelectedGlassAvailableForVehicle
) {
this.$router.navigate(
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route,
@ -321,8 +346,8 @@ export default {
resetWarningsAndErrors() {
this.displayVinNotFoundAlert = false;
this.displayMatchedDifferentVehicleAlert = false;
}
}
},
},
};
</script>

View file

@ -1,23 +1,23 @@
<template>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<questionsPageLayout
id="molding-question-wrapper"
ref="questionsPageLayout"
v-model="selectedAnswers"
isRequired
:isMetaValid="meta.valid"
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData"
:validationRules="rules.optionRequired"
:index="currentGlassIndex"
@forwardButtonAction="forwardButtonAction"
@backClick="navigateBackByVehicleQuestions" />
</Form>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<questionsPageLayout
id="molding-question-wrapper"
ref="questionsPageLayout"
v-model="selectedAnswers"
isRequired
:isMetaValid="meta.valid"
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData"
:validationRules="rules.optionRequired"
:index="currentGlassIndex"
@forwardButtonAction="forwardButtonAction"
@backClick="navigateBackByVehicleQuestions" />
</Form>
</template>
<script>
// Import Supporting Files
@ -38,7 +38,7 @@ export default {
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
questionsPageLayout
questionsPageLayout,
},
mixins: [BaseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
@ -48,8 +48,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -64,8 +64,8 @@ export default {
selectedAnswers: {},
currentGlassIndex: 0,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
optionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
@ -76,23 +76,34 @@ export default {
);
},
AlertFewMoreQuestionsCopy() {
return this.getCmsContent('AdditionalPartsQuestionsAlert', 'BodyText');
return this.getCmsContent(
'AdditionalPartsQuestionsAlert',
'BodyText'
);
},
partsOrQuestionsData() {
return useMainStore().pageData(issPageValues.MOLDING_QUESTIONS)
.partsOrQuestions;
}
},
},
mounted() {
this.getInitialQuestionData();
},
methods: {
arePagePrerequisitesValid() {
const moldingQuestionsFromPageData = useMainStore().pageData(issPageValues.MOLDING_QUESTIONS);
const moldingQuestionsFromPageData = useMainStore().pageData(
issPageValues.MOLDING_QUESTIONS
);
return (
// has childPartQuestions array and has glassName not null
moldingQuestionsFromPageData?.partsOrQuestions?.some((part) => part?.glassName)
&& moldingQuestionsFromPageData.partsOrQuestions.some((glass) => glass.parts?.some((part) => part?.childPartQuestions?.length > 0))
// has childPartQuestions array and has glassName not null
moldingQuestionsFromPageData?.partsOrQuestions?.some(
(part) => part?.glassName
) &&
moldingQuestionsFromPageData.partsOrQuestions.some((glass) =>
glass.parts?.some(
(part) => part?.childPartQuestions?.length > 0
)
)
);
},
getInitialQuestionData() {
@ -134,41 +145,44 @@ export default {
glassName: glass.glassName,
partNum: glass.answerData.answerResult,
answeredQuestions: glass.answerData.answeredQuestions,
isSuppressedPart: glass.isSuppressedPart
isSuppressedPart: glass.isSuppressedPart,
}));
// clear out answerData for future page loads; must occur prior to store save
this.questionsData.forEach((glass) => {
glass.answerData = {};
});
// save to store as order.damage.moldingQuestionArrays (array)
await this.mainStore.saveMoldingQuestionAnswers(questionAnswersArray);
await this.mainStore.saveMoldingQuestionAnswers(
questionAnswersArray
);
// get parts from the questionAnswers
const partsOrQuestions = this.partsOrQuestionsData;
// eslint-disable-next-line no-restricted-syntax
for (const answer of questionAnswersArray) {
partsOrQuestions.find((partOrQuestion) => (
partOrQuestion.glassLocation === answer.glassLocation
&& partOrQuestion.glassName === answer.glassName
)).parts[0].childParts = [
partsOrQuestions.find(
(partOrQuestion) =>
partOrQuestion.glassLocation === answer.glassLocation &&
partOrQuestion.glassName === answer.glassName
).parts[0].childParts = [
{
partNumber: answer.partNum
}
partNumber: answer.partNum,
},
];
}
this.navigateForward(partsOrQuestions, null);
}
}
},
},
};
</script>
<style lang="scss" scoped>
#molding-question-wrapper p.text-body.small {
margin-bottom: 0 !important; // Overrides extra margin-bottom on alert body text
margin-bottom: 0 !important; // Overrides extra margin-bottom on alert body text
}
#molding-question-wrapper .form-test-error {
margin-top: 0 !important; // Overrides extra margin-top on error message text
margin-top: 0 !important; // Overrides extra margin-top on error message text
}
</style>

View file

@ -32,7 +32,9 @@
overnightDropOffWidgetName="AddToCalendar_OvernightDropOff"
allDayDropOffWidgetName="AddToCalendar_AllDayDropOff"
sameDayDropOffWidgetName="AddToCalendar_SameDayDropOff"
:serviceLocationFullAddress="serviceLocationFullAddress"
:serviceLocationFullAddress="
serviceLocationFullAddress
"
:providerFullAddress="providerFullAddress"
:appointmentType="appointmentType"
:scheduleDate="appointmentDate"
@ -40,12 +42,10 @@
:scheduleEndTime="appointmentEndTime" />
<div
class="appointment-text text-center lh-base"
v-html="appointmentWordingText">
</div>
v-html="appointmentWordingText"></div>
<div
class="appointment-text text-center lh-base mt-2"
v-html="appointmentWordingText2">
</div>
v-html="appointmentWordingText2"></div>
</div>
<hr class="mb-0" />
<div>
@ -84,15 +84,20 @@ import addToCalendar from '@/layouts/order-confirmation/add-to-calendar/add-to-c
import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
// Supporting files
import { fetchCmsContentForPage, processIfStatements } from '@/helpers/cms-content-helper';
import {
fetchCmsContentForPage,
processIfStatements,
} from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store';
import { get12HourTimeFormat,
import {
get12HourTimeFormat,
get12HourTimeMobileFormat,
convertDateStringToDate,
getDisplayTextForDurationLength } from '@/helpers/date-helper.js';
getDisplayTextForDurationLength,
} from '@/helpers/date-helper.js';
import { toTitleCase } from '@/helpers/text-helper.js';
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
import applicationConfig from '@/constants/application-config';
@ -106,7 +111,7 @@ export default {
vehicleBanner,
siteFooter,
addToCalendar,
cartDropdown
cartDropdown,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -118,10 +123,9 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
@ -142,9 +146,18 @@ export default {
return this.mainStore.issConfig.successReturnURL;
},
confirmationEmailText() {
return this.getCmsContent('EmailConfirmationWordingWidget', 'BodyText')
?.replaceAll('{custom:CUSTOMER_PORTAL_URL}', applicationConfig.CUSTOMER_PORTAL_URL)
?.replaceAll('{custom:CUSTOMER_PORTAL_LOGIN_TOKEN}', this.submittedOrder.customerPortalLoginToken)
return this.getCmsContent(
'EmailConfirmationWordingWidget',
'BodyText'
)
?.replaceAll(
'{custom:CUSTOMER_PORTAL_URL}',
applicationConfig.CUSTOMER_PORTAL_URL
)
?.replaceAll(
'{custom:CUSTOMER_PORTAL_LOGIN_TOKEN}',
this.submittedOrder.customerPortalLoginToken
)
?.replaceAll('&lt;', '<')
?.replaceAll('&gt;', '>');
},
@ -173,11 +186,13 @@ export default {
return dateObject.toLocaleDateString('en-us', {
weekday: 'long',
month: 'long',
day: 'numeric'
day: 'numeric',
});
},
appointmentTimeFormatted() {
const formattedTime = this.formatAppointmentTime(this.appointmentType);
const formattedTime = this.formatAppointmentTime(
this.appointmentType
);
return formattedTime;
},
mobileWordingText() {
@ -187,7 +202,10 @@ export default {
return this.getCmsContent('MobileWordingWidget', 'BodyText2');
},
dropOffAndInShopWordingText() {
return this.getCmsContent('DropOffAndInShopWordingWidget', 'BodyText');
return this.getCmsContent(
'DropOffAndInShopWordingWidget',
'BodyText'
);
},
dropOffAndInShopWordingText2() {
return this.getBodyText2FromCms('DropOffAndInShopWordingWidget');
@ -209,13 +227,24 @@ export default {
},
serviceLocationFullAddress() {
// eslint-disable-next-line max-len
return `${this.serviceLocationAddress}, ${this.serviceLocationAddress2 ? `${this.serviceLocationAddress2},` : ''}<br/> ${this.serviceLocationCity}, ${this.serviceLocationState} ${this.serviceLocationZipCode}`;
return `${this.serviceLocationAddress}, ${
this.serviceLocationAddress2
? `${this.serviceLocationAddress2},`
: ''
}<br/> ${this.serviceLocationCity}, ${this.serviceLocationState} ${
this.serviceLocationZipCode
}`;
},
providerAddress() {
return toTitleCase(this.submittedOrder.serviceLocation.provider.address.streetAddress);
return toTitleCase(
this.submittedOrder.serviceLocation.provider.address
.streetAddress
);
},
providerCity() {
return toTitleCase(this.submittedOrder.serviceLocation.provider.address.city);
return toTitleCase(
this.submittedOrder.serviceLocation.provider.address.city
);
},
providerState() {
return this.submittedOrder.serviceLocation.provider.address.state;
@ -225,7 +254,9 @@ export default {
},
providerFullAddress() {
// eslint-disable-next-line max-len
return this.submittedOrder.serviceLocation?.provider?.address ? `${this.providerAddress},<br/> ${this.providerCity}, ${this.providerState} ${this.providerZipCode}` : '';
return this.submittedOrder.serviceLocation?.provider?.address
? `${this.providerAddress},<br/> ${this.providerCity}, ${this.providerState} ${this.providerZipCode}`
: '';
},
appointmentWordingText() {
switch (this.appointmentType) {
@ -266,14 +297,24 @@ export default {
}
},
mobileAppointment() {
return this.submittedOrder.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|| this.submittedOrder.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
return (
this.submittedOrder.serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE ||
this.submittedOrder.serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
);
},
inShopAppointment() {
return this.submittedOrder.serviceLocation.appointmentType === AppointmentTypeStrings.IN_SHOP;
return (
this.submittedOrder.serviceLocation.appointmentType ===
AppointmentTypeStrings.IN_SHOP
);
},
dropOffAppointment() {
return this.submittedOrder.serviceLocation.appointmentType === AppointmentTypeStrings.DROP_OFF;
return (
this.submittedOrder.serviceLocation.appointmentType ===
AppointmentTypeStrings.DROP_OFF
);
},
inShopAppointmentDuration() {
const inshopDurationTime = getDisplayTextForDurationLength(
@ -287,11 +328,13 @@ export default {
},
selectedVaps() {
return this.submittedOrder.lineItems.vaps;
}
},
},
mounted() {
if (this.carrierUrl) {
this.$refs.siteFooter.updateButtonText(`Go back to ${this.carrierName}`);
this.$refs.siteFooter.updateButtonText(
`Go back to ${this.carrierName}`
);
}
},
methods: {
@ -304,57 +347,62 @@ export default {
// Service Location
const { serviceLocation } = useMainStore().order;
const mobileReqs = !!(
serviceLocation.address
&& serviceLocation.city
&& serviceLocation.state
&& serviceLocation.zipCode
serviceLocation.address &&
serviceLocation.city &&
serviceLocation.state &&
serviceLocation.zipCode
);
const providerLocation = serviceLocation.provider.address;
const dropOffInShopReqs = !!(
providerLocation.streetAddress
&& providerLocation.city
&& providerLocation.state
&& providerLocation.zipCode
providerLocation.streetAddress &&
providerLocation.city &&
providerLocation.state &&
providerLocation.zipCode
);
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|| serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const isMobile =
serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE ||
serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const serviceLocationReqs =
(isMobile && mobileReqs) || (!isMobile && dropOffInShopReqs);
// Insurance
const isInsuranceSet = useMainStore().order.payment.isInsurance !== null;
const isInsuranceSet =
useMainStore().order.payment.isInsurance !== null;
// Schedule
const { schedule } = useMainStore().order;
const scheduleReqs = !!(
schedule.date
&& schedule.startTime
&& schedule.endTime
&& schedule.jobMaxMinutes
&& schedule.jobMinMinutes
schedule.date &&
schedule.startTime &&
schedule.endTime &&
schedule.jobMaxMinutes &&
schedule.jobMinMinutes
);
// Contact Info
const { contactInfo } = useMainStore().order;
const contactInfoReqs = !!(
contactInfo.firstName
&& contactInfo.lastName
&& contactInfo.servicePhone
&& contactInfo.emailAddress
contactInfo.firstName &&
contactInfo.lastName &&
contactInfo.servicePhone &&
contactInfo.emailAddress
);
// Payment
const paymentMethodReqs = useMainStore().order.payment.isPayInAdvance === false;
const paymentMethodReqs =
useMainStore().order.payment.isPayInAdvance === false;
return (
serviceLocationReqs
&& isInsuranceSet
&& scheduleReqs
&& contactInfoReqs
&& paymentMethodReqs
serviceLocationReqs &&
isInsuranceSet &&
scheduleReqs &&
contactInfoReqs &&
paymentMethodReqs
);
},
forwardButtonAction() {
@ -365,11 +413,15 @@ export default {
case AppointmentTypeStrings.MOBILE:
case AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP:
// eslint-disable-next-line max-len
return `Between ${get12HourTimeMobileFormat(this.appointmentStartTime)} - ${get12HourTimeMobileFormat(this.appointmentEndTime)}`;
return `Between ${get12HourTimeMobileFormat(
this.appointmentStartTime
)} - ${get12HourTimeMobileFormat(this.appointmentEndTime)}`;
case AppointmentTypeStrings.DROP_OFF:
return 'Drop off before 9:30 AM';
case AppointmentTypeStrings.IN_SHOP:
return `at ${get12HourTimeFormat(this.appointmentStartTime)}`;
return `at ${get12HourTimeFormat(
this.appointmentStartTime
)}`;
default:
return null;
}
@ -377,7 +429,11 @@ export default {
processIfStatements,
getBodyText2FromCms(cmsWidgetName) {
const body2Text = this.getCmsContent(cmsWidgetName, 'BodyText2');
return this.processIfStatements(body2Text, 'custom', this.getCustomValueFromString);
return this.processIfStatements(
body2Text,
'custom',
this.getCustomValueFromString
);
},
getCustomValueFromString(str) {
switch (str) {
@ -388,8 +444,8 @@ export default {
default:
return null;
}
}
}
},
},
};
</script>
<style lang="scss" scoped>

View file

@ -37,7 +37,7 @@ export default {
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
questionsPageLayout
questionsPageLayout,
},
mixins: [BaseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
@ -48,8 +48,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -65,8 +65,8 @@ export default {
selectedAnswers: {},
currentGlassIndex: 0,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
optionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
@ -78,11 +78,12 @@ export default {
},
pageData() {
return useMainStore().pageData(issPageValues.PART_QUESTIONS);
}
},
},
mounted() {
// are there alreadyAnsweredQuestions?
const alreadyAnsweredQuestions = useMainStore().damage.partQuestionAnswers;
const alreadyAnsweredQuestions =
useMainStore().damage.partQuestionAnswers;
this.questionsData = this.pageData.partsOrQuestions
.filter((x) => x.partQuestions)
@ -93,7 +94,11 @@ export default {
// reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(glass, index, alreadyAnsweredQuestions);
const updatedGlass = this.setupInitialData(
glass,
index,
alreadyAnsweredQuestions
);
// Set up watch for each set of glass questions
this.$watch(
@ -111,19 +116,23 @@ export default {
},
methods: {
arePagePrerequisitesValid() {
const partQuestionsFromPageData = useMainStore().pageData(issPageValues.PART_QUESTIONS);
const partQuestionsFromPageData = useMainStore().pageData(
issPageValues.PART_QUESTIONS
);
return (
partQuestionsFromPageData
&& Object.keys(partQuestionsFromPageData.partsOrQuestions).length > 0
partQuestionsFromPageData &&
Object.keys(partQuestionsFromPageData.partsOrQuestions).length >
0
);
},
async forwardButtonAction() {
const questionAnswersArray = this.questionsData.map((glass) => ({
glassLocation: glass.glassLocation,
glassName: glass.glassName,
result: (glass.answerData && glass.answerData.answerResult) || '',
result:
(glass.answerData && glass.answerData.answerResult) || '',
answeredQuestions: glass.answerData?.answeredQuestions,
isSuppressedPart: glass.isSuppressedPart
isSuppressedPart: glass.isSuppressedPart,
}));
// clear out answerData for future page loads; must occur prior to store save
@ -143,7 +152,7 @@ export default {
// TODO KO delete for quote mvp
this.navigateForward(glassPartsForStore, null);
}
}
},
},
};
</script>

View file

@ -5,9 +5,7 @@
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<siteHeader
ref="siteHeader"
cmsWidgetName="SiteHeaderWidget" />
<siteHeader ref="siteHeader" cmsWidgetName="SiteHeaderWidget" />
<vehicleBanner
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
@ -94,7 +92,7 @@ export default {
cartDropdown,
paymentMethodQuestion,
alert,
VehicleBanner
VehicleBanner,
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -107,20 +105,20 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'reviewDropdownData',
promise: reviewDropdownPromise
promise: reviewDropdownPromise,
},
{
resultKey: 'wipers',
promise: wipersPromise
promise: wipersPromise,
},
{
resultKey: 'rainDefense',
promise: rainDefensePromise
}
promise: rainDefensePromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -130,12 +128,15 @@ export default {
const unpricedVaps = [...resultMap.wipers, resultMap.rainDefense];
let hasBailedOut = false;
const pricedVaps = await useMainStore().getPriceOrderItems(unpricedVaps)
const pricedVaps = await useMainStore()
.getPriceOrderItems(unpricedVaps)
.catch((err) => {
useMainStore().setBailout(bailoutMessage.pricingResponseError(
unpricedVaps.map((li) => li.partNumber),
{ code: err.code, message: err.message, data: err.data }
));
useMainStore().setBailout(
bailoutMessage.pricingResponseError(
unpricedVaps.map((li) => li.partNumber),
{ code: err.code, message: err.message, data: err.data }
)
);
hasBailedOut = true;
next(`/?issPage=${issPageValues.BAILOUT_PAGE}`);
});
@ -144,7 +145,9 @@ export default {
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.updateFooterButtonText(vm.customCallToActionButtonCopy);
vm.$refs.reviewDropdown.initializeComponent(resultMap.reviewDropdownData);
vm.$refs.reviewDropdown.initializeComponent(
resultMap.reviewDropdownData
);
vm.storeAvailableVaps(pricedVaps ?? []);
});
}
@ -153,8 +156,8 @@ export default {
return {
paymentMethodInternalModel: this.getPaymentMethodFromStore(),
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
optionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
@ -171,106 +174,115 @@ export default {
}
},
displayPayInAdvanceAlert() {
return (
this.$route.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]
);
return this.$route.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT];
},
isPayInAdvanceDisabled() {
return useMainStore().isUnverified;
},
paymentMethod() {
return this.paymentMethodInternalModel;
}
},
},
watch: {
customCallToActionButtonCopy(newValue) {
this.updateFooterButtonText(newValue);
}
},
},
methods: {
arePagePrerequisitesValid() {
// Vehicle
const { vehicle } = useMainStore().order;
const vehicleReqs = !!(vehicle.year && vehicle.make && vehicle.model && vehicle.style);
const vehicleReqs = !!(
vehicle.year &&
vehicle.make &&
vehicle.model &&
vehicle.style
);
// Damage
const { isRepair, numberOfChips, glassToReplace } = useMainStore().order.damage;
const { isRepair, numberOfChips, glassToReplace } =
useMainStore().order.damage;
const damageReqs = !!(
(isRepair && numberOfChips)
|| (!isRepair && glassToReplace?.length)
(isRepair && numberOfChips) ||
(!isRepair && glassToReplace?.length)
);
// Service Package
const { lineItems } = useMainStore().order;
const packageReqs = !!(
(isRepair || lineItems.glassParts)
&& lineItems.supportingItems
(isRepair || lineItems.glassParts) &&
lineItems.supportingItems
);
// Service Location
const { serviceLocation } = useMainStore().order;
const mobileReqs = !!(
serviceLocation.address
&& serviceLocation.city
&& serviceLocation.state
&& serviceLocation.zipCode
serviceLocation.address &&
serviceLocation.city &&
serviceLocation.state &&
serviceLocation.zipCode
);
const providerLocation = serviceLocation.provider.address;
const dropOffInshopReqs = !!(
providerLocation.streetAddress
&& providerLocation.city
&& providerLocation.state
&& providerLocation.zipCode
providerLocation.streetAddress &&
providerLocation.city &&
providerLocation.state &&
providerLocation.zipCode
);
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|| serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const isMobile =
serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE ||
serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const serviceLocationReqs =
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
// Schedule
const { date, startTime, endTime, jobMaxMinutes, jobMinMinutes } = useMainStore().order.schedule;
const { date, startTime, endTime, jobMaxMinutes, jobMinMinutes } =
useMainStore().order.schedule;
const scheduleReqs = !!(
date
&& startTime
&& endTime
&& jobMaxMinutes
&& jobMinMinutes
date &&
startTime &&
endTime &&
jobMaxMinutes &&
jobMinMinutes
);
// Customer
const { firstName, lastName, emailAddress } = useMainStore().order.customer;
const customerReqs = !!(
firstName
&& lastName
&& emailAddress
);
const { firstName, lastName, emailAddress } =
useMainStore().order.customer;
const customerReqs = !!(firstName && lastName && emailAddress);
// Contact Info
const { contactInfo } = useMainStore().order;
const contactInfoReqs = !!(
contactInfo.firstName
&& contactInfo.lastName
&& contactInfo.servicePhone
&& contactInfo.emailAddress
contactInfo.firstName &&
contactInfo.lastName &&
contactInfo.servicePhone &&
contactInfo.emailAddress
);
return (
vehicleReqs
&& damageReqs
&& packageReqs
&& serviceLocationReqs
&& scheduleReqs
&& customerReqs
&& contactInfoReqs
vehicleReqs &&
damageReqs &&
packageReqs &&
serviceLocationReqs &&
scheduleReqs &&
customerReqs &&
contactInfoReqs
);
},
getPaymentMethodFromStore() {
const { payInAdvanceType } = useMainStore().order.payment;
const isPayInAdvance = useMainStore().order.payment.isPayInAdvance && !!payInAdvanceType;
const isPayInAdvance =
useMainStore().order.payment.isPayInAdvance &&
!!payInAdvanceType;
return isPayInAdvance ? payInAdvanceType : paymentMethods.PAY_AT_TIME_OF_SERVICE;
return isPayInAdvance
? payInAdvanceType
: paymentMethods.PAY_AT_TIME_OF_SERVICE;
},
updateFooterButtonText(newValue) {
this.$refs.siteFooter.updateButtonText(newValue);
@ -292,8 +304,8 @@ export default {
},
storeAvailableVaps(vaps) {
useMainStore().order.availableVaps = vaps;
}
}
},
},
};
</script>

View file

@ -1,9 +1,13 @@
<template>
<Form>
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 payment">
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<alert
v-if="displayPayInAdvanceCreditCardAlert"
name="payInAdvanceCreditCardErrorAlert"
@ -51,7 +55,6 @@
</div>
</div>
</div>
<div
v-if="shouldBlockInteraction"
class="ui-block"
@ -65,22 +68,10 @@
:target="isPaypal ? '_top' : 'card-frame'"
method="POST"
:action="checkoutUrl">
<input
type="hidden"
name="paymentType"
:value="paymentType" />
<input
type="hidden"
name="sgSessionId"
:value="authToken" />
<input
type="hidden"
name="sgAuthToken"
:value="authToken" />
<input
type="hidden"
name="sgSignaturePublic"
:value="authSignature" />
<input type="hidden" name="paymentType" :value="paymentType" />
<input type="hidden" name="sgSessionId" :value="authToken" />
<input type="hidden" name="sgAuthToken" :value="authToken" />
<input type="hidden" name="sgSignaturePublic" :value="authSignature" />
<input
type="hidden"
name="sgSignatureStartDate"
@ -93,34 +84,13 @@
type="hidden"
name="sge_commerce_indicator_isinternet"
value="true" />
<input
type="hidden"
name="sghopsource"
value="Safelite.com" />
<input
type="hidden"
name="sgHtmlStyle"
value="ResourceSafeliteHtml" />
<input
type="hidden"
name="sgErrorMessagesEmbedded"
value="true" />
<input
type="hidden"
name="sgNoKeystrokeProcessing"
value="false" />
<input
type="hidden"
name="maskCharacter"
value="*" />
<input
type="hidden"
name="styleSheetCode"
:value="dynamicCSSUrl" />
<input
type="hidden"
name="styleSheetCode2"
:value="dynamicHopCSSUrl" />
<input type="hidden" name="sghopsource" value="Safelite.com" />
<input type="hidden" name="sgHtmlStyle" value="ResourceSafeliteHtml" />
<input type="hidden" name="sgErrorMessagesEmbedded" value="true" />
<input type="hidden" name="sgNoKeystrokeProcessing" value="false" />
<input type="hidden" name="maskCharacter" value="*" />
<input type="hidden" name="styleSheetCode" :value="dynamicCSSUrl" />
<input type="hidden" name="styleSheetCode2" :value="dynamicHopCSSUrl" />
<input
type="hidden"
name="sgReceiptResponseURL"
@ -133,102 +103,33 @@
type="hidden"
name="sgErrorResponseURL"
:value="payInAdvanceResponseUrl" />
<input
type="hidden"
name="sgheaderline1"
:value="getHeaderLine1" />
<input
type="hidden"
name="sgHeader1subtitle"
value="" />
<input
type="hidden"
name="sgheaderline2"
:value="getHeaderLine2" />
<input
type="hidden"
name="sgheaderline3"
value="" />
<input
type="hidden"
name="sgheaderline4"
value="" />
<input type="hidden" name="sgheaderline1" :value="getHeaderLine1" />
<input type="hidden" name="sgHeader1subtitle" value="" />
<input type="hidden" name="sgheaderline2" :value="getHeaderLine2" />
<input type="hidden" name="sgheaderline3" value="" />
<input type="hidden" name="sgheaderline4" value="" />
<input
type="hidden"
name="sgheaderline5"
value="*Required information" />
<input
type="hidden"
name="billTo_firstName"
:value="firstName" />
<input
type="hidden"
name="billTo_firstNameShow"
value="true" />
<input
type="hidden"
name="sghopmiddleInitialShow"
value="false" />
<input
type="hidden"
name="billTo_lastName"
:value="lastName" />
<input
type="hidden"
name="billTo_lastNameShow"
value="true" />
<input
type="hidden"
name="billTo_street1"
value="" />
<input
type="hidden"
name="billTo_street2"
value="" />
<input
type="hidden"
name="billTo_city"
value="" />
<input
type="hidden"
name="billTo_state"
value="" />
<input
type="hidden"
name="billTo_postalCode"
value="" />
<input
type="hidden"
name="billTo_postalCodeShow"
value="true" />
<input
type="hidden"
name="billTo_postalCodeEnable"
value="true" />
<input
type="hidden"
name="sgLabelPostalCode"
value="Billing ZIP" />
<input
type="hidden"
name="sgtotalamount"
:value="displayAmount" />
<input
type="hidden"
name="totalAmountDecimal"
:value="totalAmount" />
<input
type="hidden"
name="lineItems"
:value="payInAdvanceLineItems" />
<input
type="hidden"
name="sgdiscountstrikethruamount"
value="" />
<input
type="hidden"
name="callerDisplayText"
value="" />
<input type="hidden" name="billTo_firstName" :value="firstName" />
<input type="hidden" name="billTo_firstNameShow" value="true" />
<input type="hidden" name="sghopmiddleInitialShow" value="false" />
<input type="hidden" name="billTo_lastName" :value="lastName" />
<input type="hidden" name="billTo_lastNameShow" value="true" />
<input type="hidden" name="billTo_street1" value="" />
<input type="hidden" name="billTo_street2" value="" />
<input type="hidden" name="billTo_city" value="" />
<input type="hidden" name="billTo_state" value="" />
<input type="hidden" name="billTo_postalCode" value="" />
<input type="hidden" name="billTo_postalCodeShow" value="true" />
<input type="hidden" name="billTo_postalCodeEnable" value="true" />
<input type="hidden" name="sgLabelPostalCode" value="Billing ZIP" />
<input type="hidden" name="sgtotalamount" :value="displayAmount" />
<input type="hidden" name="totalAmountDecimal" :value="totalAmount" />
<input type="hidden" name="lineItems" :value="payInAdvanceLineItems" />
<input type="hidden" name="sgdiscountstrikethruamount" value="" />
<input type="hidden" name="callerDisplayText" value="" />
<input
type="hidden"
name="sgtermsofusedisplaytext"
@ -261,38 +162,14 @@
type="hidden"
name="sgErrorMessageTimeOut"
value="For your security, this transaction has been timed out." />
<input
type="hidden"
name="sgLabelCardNumber"
value="" />
<input
type="hidden"
name="sgLabelAddressLine1"
:value="address1" />
<input
type="hidden"
name="sgLabelAddressLine2"
:value="address2" />
<input
type="hidden"
name="sgLabelCity"
:value="city" />
<input
type="hidden"
name="sgLabelState"
:value="state" />
<input
type="hidden"
name="sgCtu"
:value="ctu" />
<input
type="hidden"
name="sgWorkOrder"
:value="workOrderNumber" />
<input
type="hidden"
name="sgEmailAddress"
:value="emailAddress" />
<input type="hidden" name="sgLabelCardNumber" value="" />
<input type="hidden" name="sgLabelAddressLine1" :value="address1" />
<input type="hidden" name="sgLabelAddressLine2" :value="address2" />
<input type="hidden" name="sgLabelCity" :value="city" />
<input type="hidden" name="sgLabelState" :value="state" />
<input type="hidden" name="sgCtu" :value="ctu" />
<input type="hidden" name="sgWorkOrder" :value="workOrderNumber" />
<input type="hidden" name="sgEmailAddress" :value="emailAddress" />
<input
type="hidden"
name="paypalInvoiceNumber"
@ -313,30 +190,12 @@
type="hidden"
name="sgCCTimeoutURL"
:value="payInAdvanceCancelUrl" />
<input
type="hidden"
name="sgTransactionType"
value="authorization" />
<input
type="hidden"
name="amount"
:value="totalAmount" />
<input
type="hidden"
name="ctu"
:value="ctu" />
<input
type="hidden"
name="orderNumber"
:value="workOrderNumber" />
<input
type="hidden"
name="billTo_email"
:value="emailAddress" />
<input
type="hidden"
name="useDecisionManager"
value="True" />
<input type="hidden" name="sgTransactionType" value="authorization" />
<input type="hidden" name="amount" :value="totalAmount" />
<input type="hidden" name="ctu" :value="ctu" />
<input type="hidden" name="orderNumber" :value="workOrderNumber" />
<input type="hidden" name="billTo_email" :value="emailAddress" />
<input type="hidden" name="useDecisionManager" value="True" />
<input
type="hidden"
name="correlationId"
@ -345,38 +204,17 @@
type="hidden"
name="sgErrorMessageCard_CVN"
value="Please enter a valid CVV" />
<input
type="hidden"
name="ship_to_address_line1"
:value="address1" />
<input
type="hidden"
name="ship_to_address_line2"
:value="address2" />
<input
type="hidden"
name="ship_to_address_city"
:value="city" />
<input
type="hidden"
name="ship_to_address_state"
:value="state" />
<input
type="hidden"
name="ship_to_address_country"
value="US" />
<input type="hidden" name="ship_to_address_line1" :value="address1" />
<input type="hidden" name="ship_to_address_line2" :value="address2" />
<input type="hidden" name="ship_to_address_city" :value="city" />
<input type="hidden" name="ship_to_address_state" :value="state" />
<input type="hidden" name="ship_to_address_country" value="US" />
<input
type="hidden"
name="ship_to_address_postal_code"
:value="zipCode" />
<input
type="hidden"
name="ship_to_phone"
:value="phoneNumber" />
<input
type="hidden"
name="calling_application"
value="ISSNextGen" />
<input type="hidden" name="ship_to_phone" :value="phoneNumber" />
<input type="hidden" name="calling_application" value="ISSNextGen" />
</form>
</template>
<script>
@ -395,11 +233,14 @@ import { useMainStore } from '@/store';
import iframeResize from 'iframe-resizer/js/iframeResizer.js';
import externalUrls from '@/router/router-constants/externalUrl-values.js';
import widgetFields from '@/constants/cms-widget-fields.js';
import { hopPaymentMethods, paymentMethods } from '@/constants/payment-method-constants.js';
import {
hopPaymentMethods,
paymentMethods,
} from '@/constants/payment-method-constants.js';
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
import queryStrings from '@/constants/query-strings';
import alert from '@/ux-components/alert/alert.vue';
import cartDropdown from "@/iss-components/cart-dropdown/cart-dropdown.vue";
import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
export default {
name: 'payment-page',
@ -409,7 +250,7 @@ export default {
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert
alert,
},
directives: {
resize: {
@ -420,26 +261,27 @@ export default {
},
unmounted(el) {
el.iFrameResizer.removeListeners();
}
}
},
},
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const paymentSignaturePromise = await useMainStore().getPaymentSignature();
const paymentSignaturePromise =
await useMainStore().getPaymentSignature();
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'paymentSignature',
promise: paymentSignaturePromise
}
promise: paymentSignaturePromise,
},
];
// use resultMap to populate layout content.
@ -489,7 +331,7 @@ export default {
totalAmount: this.getAmountDue(),
displayAmount: this.getDisplayAmountDue(),
payInAdvanceLineItems: '',
shouldBlockInteraction: false
shouldBlockInteraction: false,
};
},
computed: {
@ -511,13 +353,19 @@ export default {
},
getHeaderLine1() {
if (!this.isPaypal) {
return this.getCmsContent('PayInAdvanceHeaderLine1', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
return this.getCmsContent(
'PayInAdvanceHeaderLine1',
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
}
return '';
},
getHeaderLine2() {
if (!this.isPaypal) {
return this.getCmsContent('PayInAdvanceHeaderLine2', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
return this.getCmsContent(
'PayInAdvanceHeaderLine2',
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
}
return '';
},
@ -532,70 +380,75 @@ export default {
},
displayPayInAdvanceAfterPayAlert() {
return this.displayPayInAdvanceAlert(paymentMethods.AFTERPAY);
}
},
},
methods:
{
methods: {
arePagePrerequisitesValid() {
// Line Items
const packageReqs = !!useMainStore().order.lineItems.supportingItems;
const packageReqs =
!!useMainStore().order.lineItems.supportingItems;
// Service Location
const { serviceLocation } = useMainStore().order;
const mobileReqs = !!(
serviceLocation.address
&& serviceLocation.city
&& serviceLocation.state
&& serviceLocation.zipCode
serviceLocation.address &&
serviceLocation.city &&
serviceLocation.state &&
serviceLocation.zipCode
);
const providerLocation = serviceLocation.provider.address;
const dropOffInshopReqs = !!(
providerLocation.streetAddress
&& providerLocation.city
&& providerLocation.state
&& providerLocation.zipCode
providerLocation.streetAddress &&
providerLocation.city &&
providerLocation.state &&
providerLocation.zipCode
);
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|| serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const isMobile =
serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE ||
serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const serviceLocationReqs =
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
// Insurance
const isInsuranceSet = useMainStore().order.payment.isInsurance !== null;
const isInsuranceSet =
useMainStore().order.payment.isInsurance !== null;
// Schedule
const { schedule } = useMainStore().order;
const scheduleReqs = !!(
schedule.date
&& schedule.startTime
&& schedule.endTime
&& schedule.jobMaxMinutes
&& schedule.jobMinMinutes
schedule.date &&
schedule.startTime &&
schedule.endTime &&
schedule.jobMaxMinutes &&
schedule.jobMinMinutes
);
// Contact Info
const { contactInfo } = useMainStore().order;
const contactInfoReqs = !!(
contactInfo.firstName
&& contactInfo.lastName
&& contactInfo.servicePhone
&& contactInfo.emailAddress
contactInfo.firstName &&
contactInfo.lastName &&
contactInfo.servicePhone &&
contactInfo.emailAddress
);
const paymentMethodReqs =
useMainStore().order.payment.isPayInAdvance !== null
&& (useMainStore().order.payment.isPayInAdvance || !!useMainStore().order.payment.payInAdvanceType);
useMainStore().order.payment.isPayInAdvance !== null &&
(useMainStore().order.payment.isPayInAdvance ||
!!useMainStore().order.payment.payInAdvanceType);
return (
packageReqs
&& serviceLocationReqs
&& isInsuranceSet
&& scheduleReqs
&& contactInfoReqs
&& paymentMethodReqs
packageReqs &&
serviceLocationReqs &&
isInsuranceSet &&
scheduleReqs &&
contactInfoReqs &&
paymentMethodReqs
);
},
getWorkOrderNumber() {
@ -616,19 +469,32 @@ export default {
return undefined;
},
getAddress1() {
return useMainStore().order.serviceLocation.address ?? useMainStore().order.serviceLocation.provider.address.streetAddress;
return (
useMainStore().order.serviceLocation.address ??
useMainStore().order.serviceLocation.provider.address
.streetAddress
);
},
getAddress2() {
return useMainStore().order.serviceLocation.address2;
},
getCity() {
return useMainStore().order.serviceLocation.city ?? useMainStore().order.serviceLocation.provider.address.city;
return (
useMainStore().order.serviceLocation.city ??
useMainStore().order.serviceLocation.provider.address.city
);
},
getState() {
return useMainStore().order.serviceLocation.state ?? useMainStore().order.serviceLocation.provider.address.state;
return (
useMainStore().order.serviceLocation.state ??
useMainStore().order.serviceLocation.provider.address.state
);
},
getZipCode() {
return useMainStore().order.serviceLocation.zipCode ?? useMainStore().order.serviceLocation.provider.address.zipCode;
return (
useMainStore().order.serviceLocation.zipCode ??
useMainStore().order.serviceLocation.provider.address.zipCode
);
},
getPaymentType() {
switch (useMainStore().order.payment.payInAdvanceType) {
@ -644,13 +510,33 @@ export default {
},
getPayInAdvanceLineItems(cartItems) {
const { glassParts } = useMainStore().order.lineItems;
const lineItems = (glassParts === null)
? [this.getPayInAdvanceFormattedLineItem('Labor', 0, 1), this.getPayInAdvanceFormattedLineItem('Repair supplies', 0, 1)]
: [this.getPayInAdvanceFormattedLineItem('Parts and labor', 0, 1)];
const lineItems =
glassParts === null
? [
this.getPayInAdvanceFormattedLineItem('Labor', 0, 1),
this.getPayInAdvanceFormattedLineItem(
'Repair supplies',
0,
1
),
]
: [
this.getPayInAdvanceFormattedLineItem(
'Parts and labor',
0,
1
),
];
cartItems.forEach((item) => {
if (item.name !== null && item.category !== 'promos') {
lineItems.push(this.getPayInAdvanceFormattedLineItem(item.name, (item.salesTax + item.subTotal).toFixed(2), 1));
lineItems.push(
this.getPayInAdvanceFormattedLineItem(
item.name,
(item.salesTax + item.subTotal).toFixed(2),
1
)
);
}
});
@ -661,9 +547,19 @@ export default {
},
getMockPiaLineItems() {
let lineItems = [];
lineItems = [this.getPayInAdvanceFormattedLineItem('Parts and labor', 0, 1)];
lineItems.push(this.getPayInAdvanceFormattedLineItem('New wiper blades', 75.22, 1));
lineItems.push(this.getPayInAdvanceFormattedLineItem('Recycling', 37.60, 1));
lineItems = [
this.getPayInAdvanceFormattedLineItem('Parts and labor', 0, 1),
];
lineItems.push(
this.getPayInAdvanceFormattedLineItem(
'New wiper blades',
75.22,
1
)
);
lineItems.push(
this.getPayInAdvanceFormattedLineItem('Recycling', 37.6, 1)
);
this.payInAdvanceLineItems = lineItems.join('||');
},
@ -671,7 +567,9 @@ export default {
return baseMixin.methods.getAmountDue(useMainStore().lineItems);
},
getDisplayAmountDue() {
return baseMixin.methods.getDisplayAmountDue(useMainStore().lineItems);
return baseMixin.methods.getDisplayAmountDue(
useMainStore().lineItems
);
},
fetchSignatureInfo(signatureInfo) {
this.authToken = signatureInfo.token;
@ -688,10 +586,21 @@ export default {
if (iframe) {
iframe.onload = () => {
if (iframe.contentWindow) {
if (this.paymentType === hopPaymentMethods.CREDIT_CARD) {
iframe.contentWindow.postMessage('CreditCardChosen', '*');
} else if (this.paymentType === hopPaymentMethods.AFTERPAY) {
iframe.contentWindow.postMessage('afterpay', '*');
if (
this.paymentType ===
hopPaymentMethods.CREDIT_CARD
) {
iframe.contentWindow.postMessage(
'CreditCardChosen',
'*'
);
} else if (
this.paymentType === hopPaymentMethods.AFTERPAY
) {
iframe.contentWindow.postMessage(
'afterpay',
'*'
);
}
}
};
@ -710,7 +619,8 @@ export default {
},
setIFrameListener() {
window.addEventListener('message', (event) =>
this.handleIFrameContentWindowMessage(event));
this.handleIFrameContentWindowMessage(event)
);
},
setUIBlock(val) {
this.shouldBlockInteraction = val;
@ -722,7 +632,8 @@ export default {
},
displayPayInAdvanceAlert(payMethod) {
return (
this.$route.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT] === payMethod
this.$route.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT] ===
payMethod
);
},
paymentFailedPayLater() {
@ -736,14 +647,17 @@ export default {
window.location = this.payInAdvanceCancelUrl;
},
forwardButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}
}
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
},
};
</script>
<style lang="scss" scoped>
.payment p{
margin-bottom:0.75rem;
.payment p {
margin-bottom: 0.75rem;
}
.hop-iframe {

View file

@ -4,13 +4,16 @@
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="SiteHeaderWidget" />
<div class="container-fluid mt-0 px-5">
<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="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
ref="siteSubHeader"
cmsWidgetName="SiteSubHeaderWidget"
@ -79,7 +82,7 @@ export default {
siteFooter,
buttonQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -89,9 +92,10 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}];
// use resultMap to populate layout content.
promise: cmsContentPromise,
},
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
@ -108,10 +112,12 @@ export default {
questionAnswersArray: [],
schoolPropertyAnswer: '',
parkingLotAnswer: '',
hasValidCarId: this.mainStore.vehicle.carId && this.mainStore.vehicle.carId !== '0',
hasValidCarId:
this.mainStore.vehicle.carId &&
this.mainStore.vehicle.carId !== '0',
rules: {
selectionRequired: globalRules.OPTION_REQUIRED
}
selectionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
@ -128,57 +134,66 @@ export default {
return this.getCmsContent('ParkingLotQuestion', 'Answers');
},
educatorEndorsement() {
return useMainStore().order.policy.endorsements?.includes(endorsementOptions.EDUCATOR) ?? false;
return (
useMainStore().order.policy.endorsements?.includes(
endorsementOptions.EDUCATOR
) ?? false
);
},
parkingGuardEndorsement() {
return useMainStore().order.policy.endorsements?.includes(endorsementOptions.PARKING_GUARD) ?? false;
}
return (
useMainStore().order.policy.endorsements?.includes(
endorsementOptions.PARKING_GUARD
) ?? false
);
},
},
methods:
{
async forwardButtonAction() {
// TO DO: remove hard coding and update data format once service returns endorsement questions
if (this.educatorEndorsement) {
this.questionAnswersArray.push({
questionNum: 1,
endorsementName: endorsementOptions.EDUCATOR,
questionText: this.schoolPropertyQuestionText,
selectedAnswer: this.schoolPropertyAnswer
});
}
if (this.parkingGuardEndorsement) {
this.questionAnswersArray.push({
questionNum: 2,
endorsementName: endorsementOptions.PARKING_GUARD,
questionText: this.parkingLotQuestionText,
selectedAnswer: this.parkingLotAnswer
});
}
// save answers to store as order.policy.endorsementQuestionAnswers
useMainStore().saveEndorsementQuestionAnswers(this.questionAnswersArray);
return this.navigateForward();
},
navigateForward() {
if (!this.hasValidCarId) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND,
this.$route
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
}
methods: {
async forwardButtonAction() {
// TO DO: remove hard coding and update data format once service returns endorsement questions
if (this.educatorEndorsement) {
this.questionAnswersArray.push({
questionNum: 1,
endorsementName: endorsementOptions.EDUCATOR,
questionText: this.schoolPropertyQuestionText,
selectedAnswer: this.schoolPropertyAnswer,
});
}
}
if (this.parkingGuardEndorsement) {
this.questionAnswersArray.push({
questionNum: 2,
endorsementName: endorsementOptions.PARKING_GUARD,
questionText: this.parkingLotQuestionText,
selectedAnswer: this.parkingLotAnswer,
});
}
// save answers to store as order.policy.endorsementQuestionAnswers
useMainStore().saveEndorsementQuestionAnswers(
this.questionAnswersArray
);
return this.navigateForward();
},
navigateForward() {
if (!this.hasValidCarId) {
this.$router.navigate(
this.navigationScenarios
.CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND,
this.$route
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
}
},
},
};
</script>
<style lang="scss" scoped>
:deep .subheader-primary {
margin-bottom: map-get($spacers, 2);
}
@ -211,5 +226,4 @@ export default {
:deep div.question-text.d-flex {
margin-top: map-get($spacers, 4);
}
</style>

View file

@ -4,39 +4,39 @@
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="container-fluid pb-2 px-5">
<div class="row mt-4 mb-3">
<div class="col">
<siteSubHeader
id="sub-header"
cmsWidgetName="SiteSubHeaderWidget" />
<addressQuestions
id="address-questions-wrapper"
ref="addressQuestions"
v-model="customerQuestions.addressQuestions"
includeStreetAddress2="true" />
<textboxQuestion
ref="policyHolderFirstName"
v-model="customerQuestions.firstName"
inputId="firstNameField"
cmsWidgetName="PolicyholderFirstNameQuestion"
isRequired
disableAutoFill
:validationRules="rules.firstName" />
<textboxQuestion
ref="policyHolderLastName"
v-model="customerQuestions.lastName"
class="mt-4"
inputId="lastNameField"
cmsWidgetName="PolicyholderLastNameQuestion"
isRequired
disableAutoFill
:validationRules="rules.lastName" />
</div>
</div>
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
id="sub-header"
cmsWidgetName="SiteSubHeaderWidget" />
<addressQuestions
id="address-questions-wrapper"
ref="addressQuestions"
v-model="customerQuestions.addressQuestions"
includeStreetAddress2="true" />
<textboxQuestion
ref="policyHolderFirstName"
v-model="customerQuestions.firstName"
inputId="firstNameField"
cmsWidgetName="PolicyholderFirstNameQuestion"
isRequired
disableAutoFill
:validationRules="rules.firstName" />
<textboxQuestion
ref="policyHolderLastName"
v-model="customerQuestions.lastName"
class="mt-4"
inputId="lastNameField"
cmsWidgetName="PolicyholderLastNameQuestion"
isRequired
disableAutoFill
:validationRules="rules.lastName" />
<siteFooter
ref="siteFooter"
class="mt-5"
@ -74,7 +74,7 @@ export default {
addressQuestions,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -85,8 +85,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
// use resultMap to populate layout content.
@ -106,8 +106,8 @@ export default {
vehiclesFound: [],
rules: {
firstName: globalRules.POLICYHOLDER_FIRST_NAME_REQUIRED,
lastName: globalRules.POLICYHOLDER_LAST_NAME_REQUIRED
}
lastName: globalRules.POLICYHOLDER_LAST_NAME_REQUIRED,
},
};
},
computed: {
@ -116,10 +116,9 @@ export default {
},
vehiclesCount() {
return this.vehiclesFound.length;
}
},
},
methods:
{
methods: {
async forwardButtonAction() {
this.mainStore.updatePolicyHolderDetails(this.customerQuestions);
return this.navigateForward();
@ -144,19 +143,20 @@ export default {
getPolicyHolderDetailsFromStore() {
return {
addressQuestions:
{
streetAddress: this.mainStore.order.customer.address.streetAddress,
streetAddress2: this.mainStore.order.customer.address.streetAddress2,
addressQuestions: {
streetAddress:
this.mainStore.order.customer.address.streetAddress,
streetAddress2:
this.mainStore.order.customer.address.streetAddress2,
city: this.mainStore.order.customer.address.city,
state: this.mainStore.order.customer.address.state,
zipCode: this.mainStore.order.customer.address.zipCode
zipCode: this.mainStore.order.customer.address.zipCode,
},
firstName: this.mainStore.order.customer.firstName,
lastName: this.mainStore.order.customer.lastName
lastName: this.mainStore.order.customer.lastName,
};
}
}
},
},
};
</script>
@ -164,14 +164,14 @@ export default {
#sub-header p {
font-size: 16px;
line-height: 26px;
color: #4D5151;
color: #4d5151;
}
#address-questions-wrapper {
margin-top: 24px;
}
#address-questions-wrapper .alert.heading {
#address-questions-wrapper .alert.heading {
line-height: 24px;
}

View file

@ -4,35 +4,32 @@
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader
class="mb-2 header"
cmsWidgetName="SiteHeaderWidget" />
<div class="select-car">
<div class="container-fluid pb-2 px-5">
<div class="row">
<div class="col">
<div class="select-car-form rounded text-center">
<vehicleBanner
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="displayGeneric"
class="mt-2 mb-4" />
<policyVehiclesQuestion
v-model="selectedVehicleVin"
class="px-4"
cmsWidgetName="PolicyVehiclesQuestion"
:vehicles="VehiclesForQuestions"
:validationRules="rules.optionRequired" />
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="backButtonAction" />
</div>
</div>
</div>
<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">
<div class="select-car-form rounded text-center">
<vehicleBanner
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="displayGeneric"
class="mt-2 mb-4" />
<policyVehiclesQuestion
v-model="selectedVehicleVin"
cmsWidgetName="PolicyVehiclesQuestion"
:vehicles="VehiclesForQuestions"
:validationRules="rules.optionRequired" />
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="backButtonAction" />
</div>
</div>
</div>
@ -55,9 +52,10 @@ import globalRules from '@/constants/global-rules.js';
import { useMainStore } from '@/store/index.js';
import bailoutMessage from '@/constants/bailoutMessage';
import {
deductibleForSelectedVehicle, endorsementsForSelectedVehicle,
deductibleForSelectedVehicle,
endorsementsForSelectedVehicle,
noCoverageForSelectedVehicle,
repairWaivedForSelectedVehicle
repairWaivedForSelectedVehicle,
} from '@/helpers/policy-vehicle-helper';
export default {
@ -68,11 +66,13 @@ export default {
vehicleBanner,
policyVehiclesQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const cmsContentPromise = await fetchCmsContentForPage(to.query.issPage);
const cmsContentPromise = await fetchCmsContentForPage(
to.query.issPage
);
next((vm) => {
vm.setCmsContent(cmsContentPromise);
});
@ -90,26 +90,27 @@ export default {
displayGeneric: true,
policyVinFound: true,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
optionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
VehiclesForQuestions() {
// Map API result data, to address-vehicles data structure
const vehicles = this.policyVehicles;
const mappedData = vehicles?.map((v) => {
const maskSymbol = 'X';
const vinStart = maskSymbol.repeat(v.vin.length - 6);
const vinEnd = v.vin.substring(v.vin.length - 6);
return {
vin: v.vin,
vehicle: v,
Text: `${v.vehicleYear} ${v.vehicleMake} ${v.vehicleModel}`,
Name: v.vin,
SubText: `VIN ${vinStart}${vinEnd}`
};
}) ?? [];
const mappedData =
vehicles?.map((v) => {
const maskSymbol = 'X';
const vinStart = maskSymbol.repeat(v.vin.length - 6);
const vinEnd = v.vin.substring(v.vin.length - 6);
return {
vin: v.vin,
vehicle: v,
Text: `${v.vehicleYear} ${v.vehicleMake} ${v.vehicleModel}`,
Name: v.vin,
SubText: `VIN ${vinStart}${vinEnd}`,
};
}) ?? [];
return mappedData;
},
noCoverageForSelectedVehicle() {
@ -125,9 +126,11 @@ export default {
return repairWaivedForSelectedVehicle(this.selectedPolicyVehicle);
},
selectedVehicle() {
const vehicle = this.mainStore.lookupVehicleByVin(this.selectedVehicleVin);
const vehicle = this.mainStore.lookupVehicleByVin(
this.selectedVehicleVin
);
return vehicle;
}
},
},
watch: {
async selectedVehicleVin(value) {
@ -151,10 +154,12 @@ export default {
// save selected vehicle to the store
this.mainStore.updateVehicle(vehicle.data);
this.displayGeneric = false;
this.selectedPolicyVehicle = this.policyVehicles.find((p) => p.vin === value);
this.selectedPolicyVehicle = this.policyVehicles.find(
(p) => p.vin === value
);
}
}
}
},
},
beforeMount() {
if (this.mainStore.order.vehicle.vin) {
@ -163,116 +168,141 @@ export default {
this.selectedVehicleVin = this.policyVehicles[0]?.vin;
}
},
methods:
{
backButtonAction() {
useMainStore().issConfig.disabledFields.policyNumber = true;
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
async forwardButtonAction() {
if (this.selectedVehicleVin !== vehicleSelectionOptions.VEHICLE_NOT_LISTED) {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.selectedVehicleVin);
const vehicle = this.policyVehicles.find((pv) => pv.vin === this.selectedVehicleVin);
methods: {
backButtonAction() {
useMainStore().issConfig.disabledFields.policyNumber = true;
this.$router.navigate(
this.navigationScenarios.CLICKED_BACK,
this.$route
);
},
async forwardButtonAction() {
if (
this.selectedVehicleVin !==
vehicleSelectionOptions.VEHICLE_NOT_LISTED
) {
const vehicleLookupResponse = await this.lookupVehicleByVin(
this.selectedVehicleVin
);
const vehicle = this.policyVehicles.find(
(pv) => pv.vin === this.selectedVehicleVin
);
if (vehicleLookupResponse.error) {
if (vehicleLookupResponse.status === 404) {
this.mainStore.resetVehicleState();
useMainStore().updateVehicle({
policyVehicleId: vehicle.id,
carId: '0',
category: '',
year: vehicle.vehicleYear || '',
make: vehicle.vehicleMake || '',
model: vehicle.vehicleModel || '',
style: vehicle.vehicleStyle || '',
vin: vehicle.vin,
noCoverage: this.noCoverageForSelectedVehicle,
deductible: this.deductibleForSelectedVehicle,
repairWaived: this.repairWaivedForSelectedVehicle
});
this.policyVinFound = false;
return this.navigateForward();
}
this.mainStore.setBailout(bailoutMessage.vehicleLookupError(vehicle.vin, vehicleLookupResponse.data));
if (vehicleLookupResponse.error) {
if (vehicleLookupResponse.status === 404) {
this.mainStore.resetVehicleState();
useMainStore().updateVehicle({
policyVehicleId: vehicle.id,
carId: '0',
category: '',
year: vehicle.vehicleYear || '',
make: vehicle.vehicleMake || '',
model: vehicle.vehicleModel || '',
style: vehicle.vehicleStyle || '',
vin: vehicle.vin,
noCoverage: this.noCoverageForSelectedVehicle,
deductible: this.deductibleForSelectedVehicle,
repairWaived: this.repairWaivedForSelectedVehicle,
});
this.policyVinFound = false;
return this.navigateForward();
}
this.vehicleFromLookup = Object.assign(vehicleLookupResponse.data, {
this.mainStore.setBailout(
bailoutMessage.vehicleLookupError(
vehicle.vin,
vehicleLookupResponse.data
)
);
return this.navigateForward();
}
this.vehicleFromLookup = Object.assign(
vehicleLookupResponse.data,
{
policyVehicleId: vehicle.id,
vin: this.selectedVehicleVin,
noCoverage: this.noCoverageForSelectedVehicle,
deductible: this.deductibleForSelectedVehicle,
repairWaived: this.repairWaivedForSelectedVehicle,
endorsements: this.endorsementsForSelectedVehicle
});
endorsements: this.endorsementsForSelectedVehicle,
}
);
useMainStore().updateVehicle(this.vehicleFromLookup);
}
return this.navigateForward();
},
navigateForward() {
if (this.mainStore.isBailout) {
this.$router
.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route,
{},
{}
);
} else if (this.selectedVehicleVin === vehicleSelectionOptions.VEHICLE_NOT_LISTED) {
this.$router
.navigate(
this.navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
this.$route,
{},
{}
);
} else if (this.endorsementsForSelectedVehicle?.length > 0
&& (this.endorsementsForSelectedVehicle?.includes(endorsementOptions.PARKING_GUARD)
|| this.endorsementsForSelectedVehicle?.includes(endorsementOptions.EDUCATOR))) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_ENDORSEMENTS,
this.$route
);
} else if (!this.policyVinFound) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND,
this.$route,
{},
{}
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_LISTED_VEHICLE,
this.$route,
{},
{}
);
}
},
async lookupVehicleByVin(vin) {
try {
return await useMainStore().lookupVehicleByVin(vin);
} catch (responseError) {
return {
error: true,
status: responseError.status,
data: responseError.data
};
}
useMainStore().updateVehicle(this.vehicleFromLookup);
}
}
return this.navigateForward();
},
navigateForward() {
if (this.mainStore.isBailout) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route,
{},
{}
);
} else if (
this.selectedVehicleVin ===
vehicleSelectionOptions.VEHICLE_NOT_LISTED
) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
this.$route,
{},
{}
);
} else if (
this.endorsementsForSelectedVehicle?.length > 0 &&
(this.endorsementsForSelectedVehicle?.includes(
endorsementOptions.PARKING_GUARD
) ||
this.endorsementsForSelectedVehicle?.includes(
endorsementOptions.EDUCATOR
))
) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_ENDORSEMENTS,
this.$route
);
} else if (!this.policyVinFound) {
this.$router.navigate(
this.navigationScenarios
.CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND,
this.$route,
{},
{}
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_LISTED_VEHICLE,
this.$route,
{},
{}
);
}
},
async lookupVehicleByVin(vin) {
try {
return await useMainStore().lookupVehicleByVin(vin);
} catch (responseError) {
return {
error: true,
status: responseError.status,
data: responseError.data,
};
}
},
},
};
</script>
<style lang="scss" scoped>
.header{
.header {
margin-bottom: 0rem !important;
}
:deep(.form-test-error){
:deep(.form-test-error) {
text-align: left;
margin-top: 0rem !important;
line-height: 1.50rem;
line-height: 1.5rem;
}
</style>
<!-- est comment -->

View file

@ -1,26 +1,32 @@
<template>
<Form
ref="theForm"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader
id="sub-header"
cmsWidgetName="SiteSubHeader"
class="mb-5 mt-4" />
<buttonQuestion
ref="buttonQuestion"
v-model="selectedProvider"
questionText="Select an option:"
:answers="prefAnswers"
groupName="prefQuestions"
buttonTypeString="providerPrefRadio"
:validationRules="rules.optionRequired"
isRequired
class="mx-5" />
<div class="px-5">
<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 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
id="sub-header"
cmsWidgetName="SiteSubHeader"
class="mb-5 mt-4" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<buttonQuestion
ref="buttonQuestion"
v-model="selectedProvider"
questionText="Select an option:"
:answers="prefAnswers"
groupName="prefQuestions"
buttonTypeString="providerPrefRadio"
:validationRules="rules.optionRequired"
isRequired />
<siteFooter
ref="siteFooter"
class="mt-5"
@ -30,28 +36,29 @@
@forwardClicked="forwardButtonAction" />
</div>
</div>
<recalModal ref="recalModal" cmsWidgetName="RecalModal" />
<steeringModal
ref="StateSteeringModal"
cmsWidgetName="StateSteeringModal" />
<shopPreferenceModal
ref="ShopPreferenceDrawer"
cmsWidgetName="ShopPreferenceDrawer"
:showSteeringLink="showSteeringLink"
@openSteering="openStateSteeringModal" />
<tpaRecalModal
ref="TPARecalModal"
cmsWidgetName="TPARecalModal"
:ackError="ackError"
@buttonClick="navigateWithTPAAck" />
</div>
<recalModal
ref="recalModal"
cmsWidgetName="RecalModal" />
<steeringModal
ref="StateSteeringModal"
cmsWidgetName="StateSteeringModal" />
<shopPreferenceModal
ref="ShopPreferenceDrawer"
cmsWidgetName="ShopPreferenceDrawer"
:showSteeringLink="showSteeringLink"
@openSteering="openStateSteeringModal" />
<tpaRecalModal
ref="TPARecalModal"
cmsWidgetName="TPARecalModal"
:ackError="ackError"
@buttonClick="navigateWithTPAAck" />
</Form>
</template>
<script>
// Import Supporting Files
import { fetchCmsContentForPage, setupModalLinks } from '@/helpers/cms-content-helper';
import {
fetchCmsContentForPage,
setupModalLinks,
} from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import errorMessages from '@/constants/error-messages';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
@ -69,7 +76,7 @@ import shopPreferenceModal from '@/layouts/provider-preference/shop-preference-m
import tpaRecalModal from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-modal.vue';
import globalRules from '@/constants/global-rules';
import bailoutCode from '@/constants/bailoutCode';
import bailoutMessage from "@/constants/bailoutMessage";
import bailoutMessage from '@/constants/bailoutMessage';
const options = { SAFELITE: 'SafeliteOption', TPA: 'TPAOption' };
@ -86,7 +93,7 @@ export default {
recalModal,
steeringModal,
shopPreferenceModal,
tpaRecalModal
tpaRecalModal,
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -95,8 +102,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
@ -114,8 +121,8 @@ export default {
showSteeringLink: false,
tpaAcknowledgement: false,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
optionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
@ -125,39 +132,58 @@ export default {
prefAnswers() {
const cmsAnswersContent = [
{
cmsWidgetName: options.SAFELITE
cmsWidgetName: options.SAFELITE,
},
{
cmsWidgetName: options.TPA
}
cmsWidgetName: options.TPA,
},
];
// if cms content has not yet loaded, skip
if (!this.getCmsContent(cmsAnswersContent[0].cmsWidgetName, 'HeaderText')
|| this.getCmsContent(cmsAnswersContent[0].cmsWidgetName, 'HeaderText') === '') {
if (
!this.getCmsContent(
cmsAnswersContent[0].cmsWidgetName,
'HeaderText'
) ||
this.getCmsContent(
cmsAnswersContent[0].cmsWidgetName,
'HeaderText'
) === ''
) {
return {};
}
const modifiedAnswers = cmsAnswersContent.map((answer) => ({
value: answer.cmsWidgetName,
buttonLabel: this.getHeaderTextFromCms(answer.cmsWidgetName),
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.cmsWidgetName),
buttonBodyCopy: this.getBodyTextFromCms(answer.cmsWidgetName)
buttonLabelSubCopy: this.getSubheaderTextFromCms(
answer.cmsWidgetName
),
buttonBodyCopy: this.getBodyTextFromCms(answer.cmsWidgetName),
}));
return modifiedAnswers;
},
ackError() {
return errorMessages.ACKNOWLEDGEMENT_REQUIRED;
}
},
},
mounted() {
setupModalLinks(this);
const pageData = this.mainStore.pageData(issPageValues.PROVIDER_PREFERENCE);
this.selectedProvider = pageData?.selectedProvider ? pageData.selectedProvider : null;
this.tpaAcknowledgement = pageData?.tpaAcknowledgement ? pageData.tpaAcknowledgement : false;
const pageData = this.mainStore.pageData(
issPageValues.PROVIDER_PREFERENCE
);
this.selectedProvider = pageData?.selectedProvider
? pageData.selectedProvider
: null;
this.tpaAcknowledgement = pageData?.tpaAcknowledgement
? pageData.tpaAcknowledgement
: false;
},
methods: {
getHeaderTextFromCms(cmsWidgetName) {
const headerText = this.getCmsContent(cmsWidgetName, 'HeaderText');
return headerText?.replace('{custom:SafeliteLogo}', "<span class='safeliteLogo'></span>");
return headerText?.replace(
'{custom:SafeliteLogo}',
"<span class='safeliteLogo'></span>"
);
},
getSubheaderTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, 'SubheaderText');
@ -174,9 +200,11 @@ export default {
navigateWithTPAAck() {
this.mainStore.saveProviderPreferenceData({
selectedProvider: this.selectedProvider,
tpaAcknowledgement: this.tpaAcknowledgement
tpaAcknowledgement: this.tpaAcknowledgement,
});
this.navigateForward(this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED);
this.navigateForward(
this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED
);
},
forwardButtonAction() {
if (this.selectedProvider) {
@ -184,7 +212,9 @@ export default {
switch (this.selectedProvider) {
case options.SAFELITE:
this.mainStore.updateIsSafeliteProvider(true);
scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE;
scenario =
this.navigationScenarios
.CLICKED_FORWARD_WITH_SAFELITE;
break;
case options.TPA:
this.mainStore.updateIsSafeliteProvider(false);
@ -194,10 +224,16 @@ export default {
this.$refs.siteFooter.removeLoader();
return;
}
scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED;
scenario =
this.navigationScenarios
.CLICKED_FORWARD_WITH_TPA_ENABLED;
} else {
this.mainStore.setBailout(bailoutMessage.TPANotEnabled());
scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED;
this.mainStore.setBailout(
bailoutMessage.TPANotEnabled()
);
scenario =
this.navigationScenarios
.CLICKED_FORWARD_WITH_TPA_DISABLED;
}
break;
default:
@ -205,14 +241,14 @@ export default {
this.navigateForward(scenario);
this.mainStore.saveProviderPreferenceData({
selectedProvider: this.selectedProvider,
tpaAcknowledgement: this.tpaAcknowledgement
tpaAcknowledgement: this.tpaAcknowledgement,
});
}
},
openStateSteeringModal() {
this.$refs.StateSteeringModal.openModal();
}
}
},
},
};
</script>
<style lang="scss" scoped>

View file

@ -4,57 +4,73 @@
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 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="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>
@ -74,10 +90,17 @@ import {
AppointmentTypeStrings,
GET_MOBILE_TIME_SLOTS,
GET_SHOP_TIME_SLOTS,
PREMIUM_FEE_PART_TYPE
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 {
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';
@ -103,7 +126,10 @@ const getAvailableDates = async (
appointmentType,
providerNumber
) => {
const apiEndDateLimit = sumDateString(startDateString, TIME_SLOTS_CALL_DAYS_LIMIT);
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 = [];
@ -117,7 +143,10 @@ const getAvailableDates = async (
if (i > 1) {
apiStartDate = sumDateString(apiEndDate, 1);
apiEndDate = sumDateString(apiStartDate, TIME_SLOTS_CALL_DAYS_LIMIT);
apiEndDate = sumDateString(
apiStartDate,
TIME_SLOTS_CALL_DAYS_LIMIT
);
if (i === apiCallsCount) {
apiEndDate = endDateString;
@ -126,14 +155,17 @@ const getAvailableDates = async (
apiEndDate = apiEndDateLimit;
}
if (appointmentType === AppointmentTypeStrings.MOBILE
|| appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
if (
appointmentType === AppointmentTypeStrings.MOBILE ||
appointmentType ===
AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
) {
storeActionConfig = {
storeAction: GET_MOBILE_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate
}
endDate: apiEndDate,
},
};
} else {
storeActionConfig = {
@ -142,15 +174,16 @@ const getAvailableDates = async (
startDate: apiStartDate,
endDate: apiEndDate,
shopAppointmentType: appointmentType,
providerNumber
}
providerNumber,
},
};
}
if (apiStartDate < apiEndDate) storeActionConfigs.push(storeActionConfig);
if (apiStartDate < apiEndDate)
storeActionConfigs.push(storeActionConfig);
}
const timeSlotsResponsesData = {
days: []
days: [],
};
function compareDayStrings(a, b) {
@ -160,26 +193,33 @@ const getAvailableDates = async (
}
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);
}
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
];
}));
timeSlotsResponsesData.estimatedServiceMinutesMinimum =
timeSlotsResponse.data.estimatedServiceMinutesMinimum;
timeSlotsResponsesData.estimatedServiceMinutesMaximum =
timeSlotsResponse.data.estimatedServiceMinutesMaximum;
timeSlotsResponsesData.days = [
...timeSlotsResponsesData.days,
...timeSlotsResponse.data.days,
];
})
);
};
return makeParallelCalls().then(() => {
@ -200,7 +240,7 @@ export default {
siteFooter,
textBlock,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -212,12 +252,13 @@ export default {
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
selectableDatesSetting: 'custom',
initialViewRowsToShow: 5,
customSelectableDatesCallback: getAvailableDates,
preSelectedDate
});
const datePickerInitialDataPromise =
await datePicker.methods.loadInitialData({
selectableDatesSetting: 'custom',
initialViewRowsToShow: 5,
customSelectableDatesCallback: getAvailableDates,
preSelectedDate,
});
const premiumFeePromise = useMainStore().getMobilePremiumFee();
@ -237,29 +278,34 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'alertReasons',
promise: alertReasonsPromise
promise: alertReasonsPromise,
},
{
resultKey: 'datePickerInitialData',
promise: datePickerInitialDataPromise
promise: datePickerInitialDataPromise,
},
{
resultKey: 'premiumFeeWithPrice',
promise: premiumFeeWithPricePromise
}
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.datePicker.initializeComponent(
resultMap.datePickerInitialData
);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
vm.setData(resultMap.datePickerInitialData.initialShopTimeSlotsResponse, resultMap.premiumFeeWithPrice);
vm.setData(
resultMap.datePickerInitialData.initialShopTimeSlotsResponse,
resultMap.premiumFeeWithPrice
);
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
});
},
@ -272,7 +318,7 @@ export default {
selectedDate: this.getSelectedDate(),
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
selectableDatesData: [],
mobilePremiumAppointmentFee: null
mobilePremiumAppointmentFee: null,
};
},
computed: {
@ -290,11 +336,13 @@ export default {
return null;
}
return this.selectableDatesData.days?.find((selectableDate) => selectableDate.date === this.selectedDate);
return this.selectableDatesData.days?.find(
(selectableDate) => selectableDate.date === this.selectedDate
);
},
supportingItems() {
return useMainStore().lineItems.supportingItems;
}
},
},
watch: {
selectedDate(newValue, oldValue) {
@ -307,31 +355,34 @@ export default {
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null
jobMinMinutes: null,
},
isPremiumAppointment: 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 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);
useMainStore().order.damage.isRepair ||
(useMainStore().order.lineItems?.glassParts != null &&
useMainStore().order.lineItems.glassParts.length > 0);
return serviceLocationPreReqs && supportingItems && damageInfo;
},
@ -349,7 +400,8 @@ export default {
this.mainStore.order.serviceLocation.provider.providerNumber
);
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesData.days = this.selectableDatesData.days.concat(newShopTimeSlots.days);
this.selectableDatesData.days =
this.selectableDatesData.days.concat(newShopTimeSlots.days);
return newShopTimeSlots;
},
getAvailableDates,
@ -364,12 +416,16 @@ export default {
},
getSelectedTimeSlotInfo() {
const isPremiumAppointment =
!!(this.supportingItems?.filter((lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE) ?? [])
.length > 0;
!!(
this.supportingItems?.filter(
(lineItem) =>
lineItem.partType === PREMIUM_FEE_PART_TYPE
) ?? []
).length > 0;
const selectedTimeSlotInfo = {
timeSlot: this.mainStore.order.schedule,
isPremiumAppointment
isPremiumAppointment,
};
return selectedTimeSlotInfo;
@ -385,12 +441,16 @@ export default {
if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
navbarButtonText = 'Continue';
} else {
navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(timeSlotInfo.timeSlot.date)}`;
navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
timeSlotInfo.timeSlot.date
)}`;
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(timeSlotInfo.timeSlot.startTime)}`;
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
timeSlotInfo.timeSlot.startTime
)}`;
} else if (
this.appointmentType === AppointmentTypeStrings.MOBILE
&& !timeSlotInfo.isPremiumAppointment
this.appointmentType === AppointmentTypeStrings.MOBILE &&
!timeSlotInfo.isPremiumAppointment
) {
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
timeSlotInfo.timeSlot.startTime,
@ -406,9 +466,15 @@ export default {
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' });
return dateObject.toLocaleDateString('en-us', {
month: 'short',
day: 'numeric',
});
},
getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) {
getDisplayTextForMilitaryTime(
militaryTimeInput,
shouldTrimMinutesIfEmpty = false
) {
// Expected input: "HH:MM"
let hours = parseInt(militaryTimeInput.split(':')[0], 10);
const minutes = militaryTimeInput.split(':')[1];
@ -426,9 +492,12 @@ export default {
forwardButtonAction() {
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}
}
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
},
};
</script>
@ -444,7 +513,8 @@ $page-side-padding: 1.5rem;
}
:deep(.text-link-small) {
a, .btn-link {
a,
.btn-link {
font-size: 0.875rem;
line-height: 1.5;
}

View file

@ -6,9 +6,7 @@
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-5" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" class="mt-5" />
<div class="main-content-container">
<serviceZipModalQuestion
ref="serviceZipCodeQuestion"
@ -86,9 +84,12 @@
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid || displayNoShopsAlert"
:isForwardActionDisabled="
!meta.valid || displayNoShopsAlert
"
@backClicked="navigateBack(this, navigateBackScenario)"
@forwardClicked="forwardButtonAction" />
>>>>>>> develop
</div>
</div>
</Form>
@ -101,7 +102,11 @@ import settleAllPromises from '@/helpers/layout-helper';
import { required } from '@/helpers/validation-rules';
import errorMessages from '@/constants/error-messages';
import { useMainStore } from '@/store';
import { getPricedMobileFeePart, getServiceabilityDetails, getZipCodeData } from '@/helpers/service-location-helper';
import {
getPricedMobileFeePart,
getServiceabilityDetails,
getZipCodeData,
} from '@/helpers/service-location-helper';
// Import Component
import alert from '@/ux-components/alert/alert.vue';
@ -119,11 +124,11 @@ import serviceZipModalQuestion from '@/layouts/service-location/service-zip-moda
// DEFINE VALIDATION RULES
defineRule('mobile-location-required', (value) => {
if (
value.addressQuestions.streetAddress === ''
|| value.addressQuestions.city === ''
|| value.addressQuestions.state === ''
|| value.addressQuestions.zipCode === ''
|| value.isVehicleProtected == null
value.addressQuestions.streetAddress === '' ||
value.addressQuestions.city === '' ||
value.addressQuestions.state === '' ||
value.addressQuestions.zipCode === '' ||
value.isVehicleProtected == null
) {
return errorMessages.MOBILE_LOCATION_REQUIRED;
}
@ -143,42 +148,46 @@ export default {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
serviceZipModalQuestion,
shopQuestion
shopQuestion,
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const serviceZipCode = useMainStore().order.serviceLocation.zipCode || useMainStore().order.customer.address.zipCode;
const serviceZipCode =
useMainStore().order.serviceLocation.zipCode ||
useMainStore().order.customer.address.zipCode;
const zipCodeData = getZipCodeData(serviceZipCode);
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
const serviceabilityDetailsPromise =
getServiceabilityDetails(serviceZipCode);
const shopQuestionInitialDataPromise =
shopQuestion.methods.loadInitialData(serviceZipCode);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'mobileFeePart',
promise: mobileFeePartPromise
promise: mobileFeePartPromise,
},
{
resultKey: 'serviceabilityDetails',
promise: serviceabilityDetailsPromise
promise: serviceabilityDetailsPromise,
},
{
resultKey: 'shopQuestionInitialData',
promise: shopQuestionInitialDataPromise
promise: shopQuestionInitialDataPromise,
},
{
resultKey: 'zipCodeData',
promise: zipCodeData
}
promise: zipCodeData,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -190,7 +199,9 @@ export default {
resultMap.mobileFeePart,
resultMap.shopQuestionInitialData?.mobileProviderNumber
);
vm.$refs.shopQuestion.initializeComponent(resultMap.shopQuestionInitialData);
vm.$refs.shopQuestion.initializeComponent(
resultMap.shopQuestionInitialData
);
});
},
setup() {
@ -214,12 +225,15 @@ export default {
mobileFeePart: null,
mobileProviderNumber: null,
zipContainsMilitaryBase: false,
zipCodeCtu: null
zipCodeCtu: null,
};
},
computed: {
questionText() {
return this.getCmsContent('ServiceTypeQuestionWidget', 'QuestionText');
return this.getCmsContent(
'ServiceTypeQuestionWidget',
'QuestionText'
);
},
answersFromCms() {
return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers');
@ -228,7 +242,7 @@ export default {
get() {
return {
state: this.state,
zipCode: this.zipCode
zipCode: this.zipCode,
};
},
set(newValue) {
@ -243,7 +257,7 @@ export default {
// eslint-disable-next-line vue/valid-next-tick
this.$nextTick();
}
},
},
mobileLocationQuestions: {
get() {
@ -253,9 +267,9 @@ export default {
streetAddress2: this.streetAddress2,
city: this.city,
state: this.state,
zipCode: this.zipCode
zipCode: this.zipCode,
},
isVehicleProtected: this.isVehicleProtected
isVehicleProtected: this.isVehicleProtected,
};
},
set(newValue) {
@ -267,46 +281,62 @@ export default {
this.isVehicleProtected = newValue.isVehicleProtected;
if (newValue.zipCode !== this.zipCode) {
if (!(this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)) {
if (
!(
this.selectedAppointmentType ===
AppointmentTypeStrings.MOBILE ||
this.selectedAppointmentType ===
AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
)
) {
this.selectedAppointmentType = null;
}
this.selectedProvider = null;
}
}
},
},
isServiceableMobile() {
if (this.isRecalibrationServiceableMobile !== null) {
return this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile;
return (
this.isGlassServiceableMobile &&
this.isRecalibrationServiceableMobile
);
}
return this.isGlassServiceableMobile;
},
isServiceableInshop() {
if (this.isRecalibrationServiceableInshop !== null) {
return this.isGlassServiceableInshop && this.isRecalibrationServiceableInshop;
return (
this.isGlassServiceableInshop &&
this.isRecalibrationServiceableInshop
);
}
return this.isGlassServiceableInshop;
},
isShopQuestionDisplayed() {
return (
this.selectedAppointmentType === 'Inshop'
|| this.selectedAppointmentType === 'Dropoff'
this.selectedAppointmentType === 'Inshop' ||
this.selectedAppointmentType === 'Dropoff'
);
},
isAppointmentTypeDisplayed() {
return this.zipCode && !this.displayNoShopsAlert;
},
isMobileLocationDisplayed() {
return this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
return (
this.selectedAppointmentType ===
AppointmentTypeStrings.MOBILE ||
this.selectedAppointmentType ===
AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
);
},
requiresInshopRecalibration() {
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
return (
this.isServiceableInshop
&& this.isGlassServiceableMobile
&& this.isRecalibrationServiceableMobile === false
this.isServiceableInshop &&
this.isGlassServiceableMobile &&
this.isRecalibrationServiceableMobile === false
);
},
displayMilitaryZipAlert() {
@ -320,9 +350,9 @@ export default {
},
displayServiceableInshopOnly() {
return (
!this.displayRecalibrationWarning
&& this.isServiceableInshop
&& !this.isServiceableMobile
!this.displayRecalibrationWarning &&
this.isServiceableInshop &&
!this.isServiceableMobile
);
},
displayServiceableMobileOnly() {
@ -333,13 +363,13 @@ export default {
return isNoComp || isITAC
? this.navigationScenarios.CLICKED_BACK_CANNOT_REACH_TPA_FLOW
: this.navigationScenarios.CLICKED_BACK_CAN_REACH_TPA_FLOW;
}
},
},
methods: {
arePagePrerequisitesValid() {
return (
useMainStore().lineItems.supportingItems !== null
&& useMainStore().order.serviceLocation.zipCode !== null
useMainStore().lineItems.supportingItems !== null &&
useMainStore().order.serviceLocation.zipCode !== null
);
},
async reloadShopData(zipCode) {
@ -360,8 +390,8 @@ export default {
city: null,
state: null,
zipCode: null,
zipCodeCtu: null
}
zipCodeCtu: null,
},
};
}
@ -374,17 +404,18 @@ export default {
zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType,
isVehicleProtected: this.isVehicleProtected,
provider
provider,
});
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
openModalAction(modalName) {
this.$refs[modalName].openModal();
},
resetDependentState() {
},
resetDependentState() {},
getServiceAddressFromStore() {
return useMainStore().order.serviceLocation.address;
},
@ -395,10 +426,16 @@ export default {
return useMainStore().order.serviceLocation.city;
},
getServiceStateFromStore() {
return useMainStore().order.serviceLocation.state || useMainStore().order.customer.address.state;
return (
useMainStore().order.serviceLocation.state ||
useMainStore().order.customer.address.state
);
},
getServiceZipCodeFromStore() {
return useMainStore().order.serviceLocation.zipCode || useMainStore().order.customer.address.zipCode;
return (
useMainStore().order.serviceLocation.zipCode ||
useMainStore().order.customer.address.zipCode
);
},
getIsVehicleProtectedFromStore() {
return useMainStore().order.serviceLocation.isVehicleProtected;
@ -409,7 +446,12 @@ export default {
getSelectedProvider() {
return useMainStore().order.serviceLocation.provider;
},
setData(zipCodeData, serviceabilityDetails, mobileFeePart, mobileProviderNumber) {
setData(
zipCodeData,
serviceabilityDetails,
mobileFeePart,
mobileProviderNumber
) {
if (zipCodeData) {
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
this.zipCodeCtu = zipCodeData.zipCodeCtu;
@ -446,12 +488,16 @@ export default {
this.isVehicleProtected = null;
},
setServiceabilityDetails(serviceabilityDetails) {
this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop;
this.isRecalibrationServiceableInshop = serviceabilityDetails.isRecalibrationServiceableInshop;
this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile;
this.isRecalibrationServiceableMobile = serviceabilityDetails.isRecalibrationServiceableMobile;
}
}
this.isGlassServiceableInshop =
serviceabilityDetails.isGlassServiceableInshop;
this.isRecalibrationServiceableInshop =
serviceabilityDetails.isRecalibrationServiceableInshop;
this.isGlassServiceableMobile =
serviceabilityDetails.isGlassServiceableMobile;
this.isRecalibrationServiceableMobile =
serviceabilityDetails.isRecalibrationServiceableMobile;
},
},
};
</script>
@ -475,21 +521,21 @@ $page-side-padding: 1.5rem;
&:first-of-type {
line-height: 1.5rem;
}
&:not(:nth-of-type(1)){
line-height: 1.250rem;
&:not(:nth-of-type(1)) {
line-height: 1.25rem;
}
}
.choose-option{
.button-question >div {
.choose-option {
.button-question > div {
&:first-of-type {
margin-bottom: 0.95rem;
line-height: 1.500rem;
line-height: 1.5rem;
}
}
}
.button-question{
.row.form-test-error{
line-height:1.500rem;
.button-question {
.row.form-test-error {
line-height: 1.5rem;
padding-left: 0rem !important;
}
}
@ -500,7 +546,6 @@ $page-side-padding: 1.5rem;
span {
text-align: center;
}
}
}
</style>

View file

@ -4,14 +4,18 @@
v-slot="{ meta }"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition sub-container make-tall">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
justification="left"
issContainingPage="service-packages" />
<div class="mx-5">
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
justification="left"
issContainingPage="service-packages" />
<servicePackageQuestion
ref="servicePackage"
cmsWidgetName="ServicePackage"
@ -31,22 +35,18 @@
@ForwardClicked="forwardButtonAction" />
</div>
</div>
<loadingModal ref="loadingModal" :textSlides="loadingText" />
<contentGroupModal
ref="RainDefenseModal"
cmsWidgetName="RainDefenseModal" />
<contentGroupModal
ref="FrontWiperModal"
cmsWidgetName="FrontWiperModal" />
<contentGroupModal
ref="RearWiperModal"
cmsWidgetName="RearWiperModal" />
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
</div>
<loadingModal
ref="loadingModal"
:textSlides="loadingText" />
<contentGroupModal
ref="RainDefenseModal"
cmsWidgetName="RainDefenseModal" />
<contentGroupModal
ref="FrontWiperModal"
cmsWidgetName="FrontWiperModal" />
<contentGroupModal
ref="RearWiperModal"
cmsWidgetName="RearWiperModal" />
<contentGroupModal
ref="RecalModal"
cmsWidgetName="RecalModal" />
</Form>
</template>
@ -81,7 +81,7 @@ export default {
Form,
servicePackageQuestion,
loadingModal,
contentGroupModal
contentGroupModal,
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -94,20 +94,20 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'wipers',
promise: wipersPromise
promise: wipersPromise,
},
{
resultKey: 'rainDefense',
promise: rainDefensePromise
promise: rainDefensePromise,
},
{
resultKey: 'supportingItems',
promise: supportingItemsPromise
}
promise: supportingItemsPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -118,11 +118,12 @@ export default {
resultMap.rainDefense,
...resultMap.supportingItems,
...resultMap.wipers,
...clonedGlassParts
...clonedGlassParts,
];
let hasBailedOut = false;
const pricingResults = await store.getPriceOrderItems(availableLineItems)
const pricingResults = await store
.getPriceOrderItems(availableLineItems)
.catch((err) => {
useMainStore().setBailout(
bailoutMessage.pricingResponseError(
@ -155,17 +156,17 @@ export default {
'Looking for dates',
'Searching for times',
'Nearly there',
'Finishing up'
'Finishing up',
],
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
optionRequired: globalRules.OPTION_REQUIRED,
},
};
},
computed: {
PriceDisclaimerText() {
return this.getCmsContent('PriceDisclaimerWidget', 'Text');
}
},
},
methods: {
openModalAction(modalName) {
@ -173,11 +174,11 @@ export default {
},
arePagePrerequisitesValid() {
return (
store.order.serviceLocation.zipCode
&& store.order.serviceLocation.zipCodeCtu
&& (store.order.damage.isRepair
|| (store.order.lineItems?.glassParts != null
&& store.order.lineItems.glassParts.length > 0))
store.order.serviceLocation.zipCode &&
store.order.serviceLocation.zipCodeCtu &&
(store.order.damage.isRepair ||
(store.order.lineItems?.glassParts != null &&
store.order.lineItems.glassParts.length > 0))
);
},
vapsItemsSelectedAction(vapsItemsSelected) {
@ -187,10 +188,12 @@ export default {
const parts = {
glassParts: this.pricedGlassParts,
supportingItems: this.supportingItems,
vaps: this.selectedVaps
vaps: this.selectedVaps,
};
if (!allGlassPartsAndItemsHavePrices(parts)) {
window.console.error('One or more items have no price assigned!');
window.console.error(
'One or more items have no price assigned!'
);
}
if (this.pricedGlassParts.length > 0) {
store.updateGlassParts(this.pricedGlassParts);
@ -198,15 +201,18 @@ export default {
store.updateSupportingItems(this.supportingItems);
store.updateVaps(this.selectedVaps);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}
}
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
},
};
</script>
<style lang="scss" scoped>
.subheader-secondary {
margin-top: .5rem;
margin-top: 0.5rem;
padding: 0px;
}
@ -214,7 +220,7 @@ export default {
margin: 1.3rem 0 1.5rem 0;
a {
color: #4D5151;
color: #4d5151;
font-weight: 400;
}
}

View file

@ -4,12 +4,16 @@
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="SiteHeaderWidget" />
<div class="container-fluid px-5 pb-2 confirmation">
<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="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
ref="vehicleBanner"
class="mb-4"
@ -17,7 +21,9 @@
:displayGenericVehicleImage="false" />
<div class="text-center text-color--black pb-2 fs-5">
<img :src="tpaConfirmationImage" />
<span class="ms-2" v-html="tpaConfirmationHeaderText"></span>
<span
class="ms-2"
v-html="tpaConfirmationHeaderText"></span>
</div>
<div class="text-center text-color--black mb-3 fw-bold">
<span v-html="tpaConfirmationSubheaderText"></span>
@ -36,8 +42,7 @@
:customText="contactCarrierText"
class="contact-carrier-text small"
@click="setBailoutInfo()" />
<div
ref="confirmationOrderDetailsSection">
<div ref="confirmationOrderDetailsSection">
<textBlock
ref="tpaConfirmationOrderDetailsTitle"
:customText="orderDetailsTitle"
@ -75,12 +80,19 @@ import textBlock from '@/digital-components/text-block/text-block.vue';
import deductibleBox from '@/layouts/tpa-submit/deductible-box/deductible-box.vue';
// Supporting files
import { fetchCmsContentForPage, processIfStatements } from '@/helpers/cms-content-helper';
import {
fetchCmsContentForPage,
processIfStatements,
} from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store';
import { toTitleCase, toDisplayPhoneNumber, formatAmountInDollars } from '@/helpers/text-helper.js';
import {
toTitleCase,
toDisplayPhoneNumber,
formatAmountInDollars,
} from '@/helpers/text-helper.js';
import bailoutMessage from '@/constants/bailoutMessage';
const VERIFYING_COVERAGE = 'Verifying coverage';
@ -94,7 +106,7 @@ export default {
textBlock,
deductibleBox,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -105,12 +117,12 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'accountInfo',
promise: accountInfoPromise
}
promise: accountInfoPromise,
},
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
@ -130,40 +142,67 @@ export default {
return this.getCmsContent('TPAConfirmationContent', 'Image');
},
tpaConfirmationSubheaderText() {
return this.getCmsContent('TPAConfirmationContent', 'SubheaderText')
?.replaceAll('{custom:glassShop}', this.preferredShopName);
return this.getCmsContent(
'TPAConfirmationContent',
'SubheaderText'
)?.replaceAll('{custom:glassShop}', this.preferredShopName);
},
tpaConfirmationBodyOne() {
return this.getCmsContent('TPAConfirmationContent', 'BodyText')
?.replaceAll('{custom:phoneNumber}', this.preferredShopPhoneNumber)
?.replaceAll(
'{custom:phoneNumber}',
this.preferredShopPhoneNumber
)
?.replaceAll('{custom:glassShop}', this.preferredShopName);
},
tpaConfirmationBodyTwo() {
return this.getCmsContent('TPAConfirmationContent', 'BodyText2');
},
contactCarrierText() {
const contactCarrierText = this.getCmsContent('ContactCarrierContent', 'Text')
?.replaceAll('{custom:carrierPhoneNumber}', this.carrierPhoneNumber);
return this.processIfStatements(contactCarrierText, 'custom', this.getCustomValueFromString);
const contactCarrierText = this.getCmsContent(
'ContactCarrierContent',
'Text'
)?.replaceAll(
'{custom:carrierPhoneNumber}',
this.carrierPhoneNumber
);
return this.processIfStatements(
contactCarrierText,
'custom',
this.getCustomValueFromString
);
},
orderDetailsTitle() {
return this.getCmsContent('OrderDetailsContent', 'HeaderText');
},
orderDetailsBody() {
const orderDetailsBodyText = this.getCmsContent('OrderDetailsContent', 'BodyText');
return this.processIfStatements(orderDetailsBodyText, 'custom', this.getCustomValueFromString);
const orderDetailsBodyText = this.getCmsContent(
'OrderDetailsContent',
'BodyText'
);
return this.processIfStatements(
orderDetailsBodyText,
'custom',
this.getCustomValueFromString
);
},
deductibleBoxValue() {
return this.isVerified ? this.formatAmountInDollars(this.currentDeductible) : VERIFYING_COVERAGE;
return this.isVerified
? this.formatAmountInDollars(this.currentDeductible)
: VERIFYING_COVERAGE;
},
currentDeductible() {
return useMainStore().order.currentDeductible;
},
preferredShopName() {
return toTitleCase(useMainStore().order.serviceLocation.provider.companyName);
return toTitleCase(
useMainStore().order.serviceLocation.provider.companyName
);
},
preferredShopPhoneNumber() {
return this.toDisplayPhoneNumber(useMainStore().order.serviceLocation.provider.phoneNumber);
return this.toDisplayPhoneNumber(
useMainStore().order.serviceLocation.provider.phoneNumber
);
},
isVerified() {
return useMainStore().order.payment.insuranceCoverage.isVerified;
@ -175,44 +214,46 @@ export default {
return this.mainStore.issConfig.successReturnURL;
},
carrierPhoneNumber() {
return this.toDisplayPhoneNumber(useMainStore().order.carrierPhoneNumber);
}
return this.toDisplayPhoneNumber(
useMainStore().order.carrierPhoneNumber
);
},
},
mounted() {
if (this.carrierUrl) {
this.$refs.siteFooter.updateButtonText(`Go back to ${this.carrierName}`);
this.$refs.siteFooter.updateButtonText(
`Go back to ${this.carrierName}`
);
}
},
methods:
{
async forwardButtonAction() {
this.$router.navigateToExternalUrl(this.carrierUrl);
},
getCustomValueFromString(str) {
switch (str) {
case 'deductibleAboveZero':
return this.isVerified && this.currentDeductible !== 0;
case 'zeroDeductible':
return this.isVerified && this.currentDeductible === 0;
case 'verifyingCoverage':
return !this.isVerified;
case 'coverageVerified':
return this.isVerified;
default:
return null;
}
},
setBailoutInfo() {
this.mainStore.setBailout(bailoutMessage.RequestCallback);
},
toDisplayPhoneNumber,
formatAmountInDollars,
processIfStatements
}
methods: {
async forwardButtonAction() {
this.$router.navigateToExternalUrl(this.carrierUrl);
},
getCustomValueFromString(str) {
switch (str) {
case 'deductibleAboveZero':
return this.isVerified && this.currentDeductible !== 0;
case 'zeroDeductible':
return this.isVerified && this.currentDeductible === 0;
case 'verifyingCoverage':
return !this.isVerified;
case 'coverageVerified':
return this.isVerified;
default:
return null;
}
},
setBailoutInfo() {
this.mainStore.setBailout(bailoutMessage.RequestCallback);
},
toDisplayPhoneNumber,
formatAmountInDollars,
processIfStatements,
},
};
</script>
<style lang="scss" scoped>
.text-color--black {
color: $black;
}
@ -233,5 +274,4 @@ export default {
font-weight: $font-weight-bold;
}
}
</style>

View file

@ -1,112 +1,120 @@
<template>
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition">
<siteHeader
ref="siteHeader"
:cmsWidgetName="widget.siteHeader" />
<Form
id="searchProvidersForm"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="container-fluid pb-2">
<div class="mx-5">
<label
id="tpaSearchQuestionLabel"
for="tpaSearchQuestionField"
class="text-center fs-5 mt-5 mb-0 text-black w-100">
{{ tpaSearchQuestionLabel }}
</label>
<label
id="searchInstructions"
for="tpaSearchQuestionField"
class="text-center small darker-gray w-100 mb-4">
{{ searchInstructionsText }}
</label>
<div class="mb-5">
<textboxQuestion
id="tpaSearchQuestionField"
v-model="zipCode"
inputId="tpaSearchQuestionFieldInput"
:cmsWidgetName="widget.tpaSearchQuestion"
:includeSearchIcon="true"
:displayQuestionText="false"
isRequired
:isDisabled="reloadingProviders"
:validationRules="rules.zipCode"
@clickEvent="searchClick" />
</div>
</div>
</div>
</Form>
<googleMap
id="map"
class="mb-4"
:addresses="providerAddresses"
:zipCode="mapZipCode" />
<Form
id="providerSelectionForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="container-fluid pb-2">
<div class="mx-5">
<dropdownQuestion
id="searchRadiusFilter"
v-model="filter"
:cmsWidgetName="widget.filterByQuestion"
inputId="searchRadiusFilterInput"
:options="filterOptions"
disableAutoFill
<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>
</div>
</div>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<Form
id="searchProvidersForm"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<label
id="tpaSearchQuestionLabel"
for="tpaSearchQuestionField"
class="text-center fs-5 mt-5 mb-0 text-black w-100">
{{ tpaSearchQuestionLabel }}
</label>
<label
id="searchInstructions"
for="tpaSearchQuestionField"
class="text-center small darker-gray w-100 mb-4">
{{ searchInstructionsText }}
</label>
<div class="mb-5">
<textboxQuestion
id="tpaSearchQuestionField"
v-model="zipCode"
inputId="tpaSearchQuestionFieldInput"
:cmsWidgetName="widget.tpaSearchQuestion"
:includeSearchIcon="true"
:displayQuestionText="false"
isRequired
:isDisabled="reloadingProviders"
:validationRules="rules.filter" />
<loader
v-if="reloadingProviders"
id="providersLoader"
loaderPosition="center"
loaderColor="blue"
:width="2"
:height="2"
class="my-4" />
<div v-else>
<div class="my-4">
<buttonQuestion
id="selectProviderQuestion"
v-model="selectedProviderNumber"
buttonTypeString="shopListButton"
:buttonTypeObject="shopListButton"
class="radioQuestion"
:answers="providerButtonData"
groupName="chooseShop"
textPosition="text-start"
isRequired
:validationRules="rules.provider"
:additionalButtonData="additionalButtonData" />
<alert
v-if="providers?.length === 0 ?? true"
id="alertNoNetworkProviders"
:cmsWidgetName="widget.noNetworkShopsAlert"
alertClass="alert-warning"
:isDismissible="false"
:manualHeadline="noNetworkShopsAlertHeaderText" />
</div>
<div class="text-center">
<textLink
id="preferredShopNotListedLink"
linkType="navigation"
href="javascript:void(0)"
:text="shopNotListedModalLink"
@clickEvent="doNotSeeMyShopLinkClick" />
</div>
</div>
:validationRules="rules.zipCode"
@clickEvent="searchClick" />
</div>
<siteFooter
ref="siteFooter"
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
</Form>
</Form>
</div>
</div>
</div>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<googleMap
id="map"
class="mb-4"
:addresses="providerAddresses"
:zipCode="mapZipCode" />
<Form
id="providerSelectionForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<dropdownQuestion
id="searchRadiusFilter"
v-model="filter"
:cmsWidgetName="widget.filterByQuestion"
inputId="searchRadiusFilterInput"
:options="filterOptions"
disableAutoFill
:isDisabled="reloadingProviders"
:validationRules="rules.filter" />
<loader
v-if="reloadingProviders"
id="providersLoader"
loaderPosition="center"
loaderColor="blue"
:width="2"
:height="2"
class="my-4" />
<div v-else>
<div class="my-4">
<buttonQuestion
id="selectProviderQuestion"
v-model="selectedProviderNumber"
buttonTypeString="shopListButton"
:buttonTypeObject="shopListButton"
class="radioQuestion"
:answers="providerButtonData"
groupName="chooseShop"
textPosition="text-start"
isRequired
:validationRules="rules.provider"
:additionalButtonData="additionalButtonData" />
<alert
v-if="providers?.length === 0 ?? true"
id="alertNoNetworkProviders"
:cmsWidgetName="widget.noNetworkShopsAlert"
alertClass="alert-warning"
:isDismissible="false"
:manualHeadline="
noNetworkShopsAlertHeaderText
" />
</div>
<div class="text-center">
<textLink
id="preferredShopNotListedLink"
linkType="navigation"
href="javascript:void(0)"
:text="shopNotListedModalLink"
@clickEvent="doNotSeeMyShopLinkClick" />
</div>
<siteFooter
ref="siteFooter"
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
</Form>
</div>
</div>
</div>
</template>
@ -138,7 +146,7 @@ const radiusFilterPairs = [
{ radius: 15, filter: '15 miles' },
{ radius: 25, filter: '25 miles' },
{ radius: 50, filter: '50 miles' },
{ radius: 100, filter: '100 miles' }
{ radius: 100, filter: '100 miles' },
];
function convertRadiusFilterToInteger(filter) {
@ -194,11 +202,12 @@ export default {
googleMap,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const { zipCode, filter, providers, providerNumber } = await getInitialSearchData();
const { zipCode, filter, providers, providerNumber } =
await getInitialSearchData();
const cmsContent = await fetchCmsContentForPage(to.query.issPage);
next(async (vm) => {
@ -216,7 +225,7 @@ export default {
selectedProviderNumber: '',
reloadingProviders: false,
additionalButtonData: {
displayAvailabilityIndicators: false
displayAvailabilityIndicators: false,
},
widget: {
siteHeader: 'SiteHeaderWidget',
@ -225,22 +234,23 @@ export default {
filterByQuestion: 'FilterByQuestion',
noNetworkShopsAlert: 'NoNetworkShopsAlertWidget',
shopNotListedLink: 'ShopNotListedLink',
siteFooter: 'SiteFooterWidget'
siteFooter: 'SiteFooterWidget',
},
rules: {
zipCode: `${globalRules.ZIP_CODE_REQUIRED}|${globalRules.ZIP_CODE_SEARCH_FORMAT}`,
filter: globalRules.OPTION_REQUIRED, // TODO do we even need this?
provider: globalRules.OPTION_REQUIRED
provider: globalRules.OPTION_REQUIRED,
},
shopListButton: shallowRef(shopListButton)
shopListButton: shallowRef(shopListButton),
};
},
computed: {
filterOptions() {
const filterByAnswers = this.getCmsContent(
this.widget.filterByQuestion,
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
) ?? [];
const filterByAnswers =
this.getCmsContent(
this.widget.filterByQuestion,
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
) ?? [];
const filterByAnswersObj = {};
[...filterByAnswers].forEach((answer) => {
filterByAnswersObj[answer.Name] = answer.Name;
@ -275,14 +285,26 @@ export default {
)?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles);
},
providerAddresses() {
return this.providers?.map((provider) => this.getProviderAddress(provider)) ?? [];
return (
this.providers?.map((provider) =>
this.getProviderAddress(provider)
) ?? []
);
},
providerButtonData() {
return this.providers?.map((provider) => this.getShopButtonDataFromProvider(provider)) ?? [];
return (
this.providers?.map((provider) =>
this.getShopButtonDataFromProvider(provider)
) ?? []
);
},
selectedProviderIsSafeliteShop() {
return this.providers?.find((p) => p.providerNumber === this.selectedProviderNumber)?.isSafeliteShop ?? false;
}
return (
this.providers?.find(
(p) => p.providerNumber === this.selectedProviderNumber
)?.isSafeliteShop ?? false
);
},
},
watch: {
async filter() {
@ -295,13 +317,16 @@ export default {
},
providers(newProviders) {
if (this.dataLoaded) {
this.selectedProviderNumber = newProviders?.length === 1 ?? false
? newProviders[0]?.providerNumber ?? ''
: '';
this.selectedProviderNumber =
newProviders?.length === 1 ?? false
? newProviders[0]?.providerNumber ?? ''
: '';
}
},
selectedProviderNumber(newNumber) {
const provider = this.providers?.find((p) => p.providerNumber === newNumber);
const provider = this.providers?.find(
(p) => p.providerNumber === newNumber
);
if (provider && this.dataLoaded) {
useMainStore().updateServiceLocation({
searchFilter: this.filter,
@ -313,14 +338,14 @@ export default {
city: provider.address?.city,
state: provider.address?.state,
zipCode: provider.address?.zipCode,
zipCodeCtu: provider.address?.zipCodeCtu
zipCodeCtu: provider.address?.zipCodeCtu,
},
companyName: provider?.companyName,
phoneNumber: provider?.phoneNumber
}
phoneNumber: provider?.phoneNumber,
},
});
}
}
},
},
beforeUpdate() {
if (!this.dataLoaded) {
@ -355,12 +380,16 @@ export default {
}
const addressLine1 = toTitleCase(provider?.address?.streetAddress);
const joinString = addressLine1.length > 0 && addressLine2.length > 0 ? ', ' : '';
const joinString =
addressLine1.length > 0 && addressLine2.length > 0 ? ', ' : '';
return [addressLine1, addressLine2].join(joinString);
},
async getProviderButtonData() {
this.reloadingProviders = true;
const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles);
const getTpaProvidersResult = await useMainStore().getTpaProviders(
this.zipCode,
this.radiusInMiles
);
this.reloadingProviders = false;
return getTpaProvidersResult?.data?.shopProviders ?? [];
},
@ -381,7 +410,8 @@ export default {
}
const scenario = this.selectedProviderIsSafeliteShop
? this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP
: this.navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP;
: this.navigationScenarios
.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP;
this.$router.navigate(scenario, this.$route);
},
getCustomValueFromString(str) {
@ -394,25 +424,27 @@ export default {
},
getShopButtonDataFromProvider(provider) {
const cellNumber = toDisplayPhoneNumber(provider?.phoneNumber);
const distance = provider?.distanceInMiles !== null && !Number.isNaN(parseFloat(provider?.distanceInMiles))
? +provider.distanceInMiles.toFixed(1)
: null;
const distance =
provider?.distanceInMiles !== null &&
!Number.isNaN(parseFloat(provider?.distanceInMiles))
? +provider.distanceInMiles.toFixed(1)
: null;
return {
buttonLabel: provider?.companyName ?? '',
buttonLabelSubCopy: distance === null
? ''
: `${distance} mi`,
buttonBodyCopy: `${this.getProviderAddress(provider)}<br>${cellNumber ?? ''}`,
value: provider?.providerNumber ?? ''
buttonLabelSubCopy: distance === null ? '' : `${distance} mi`,
buttonBodyCopy: `${this.getProviderAddress(provider)}<br>${
cellNumber ?? ''
}`,
value: provider?.providerNumber ?? '',
};
}
}
},
},
};
</script>
<style lang="scss" scoped>
.darker-gray {
color: map-get($colors, "darker-gray");
color: map-get($colors, 'darker-gray');
}
</style>

View file

@ -5,17 +5,22 @@
class="tpa-submit"
@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="px-5">
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
ref="vehicleBanner"
:cmsWidgetName="widget.vehicleBanner"
:displayGenericVehicleImage="false"
class="mt-5" /> <!-- TODO fix styling -->
class="mt-5" />
<!-- TODO fix styling -->
<textBlock
ref="subHeaderTitle"
:customText="subHeaderTitle"
@ -51,9 +56,7 @@
<div
v-for="(section, index) in sections"
:key="section.title">
<hr
v-if="index !== 0"
class="my-3" />
<hr v-if="index !== 0" class="my-3" />
<reviewBlock
:id="'review-block-' + index"
:customHeaderText="section.title"
@ -109,13 +112,25 @@ import contactDetailsDrawer from '@/layouts/tpa-submit/contact-details-drawer/co
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
// Supporting files
import { fetchCmsContentForPage, processIfStatements, getStringWithCustomValues } from '@/helpers/cms-content-helper.js';
import {
fetchCmsContentForPage,
processIfStatements,
getStringWithCustomValues,
} from '@/helpers/cms-content-helper.js';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import widgetFields from '@/constants/cms-widget-fields.js';
import { useMainStore } from '@/store';
import { toTitleCase, toDisplayPhoneNumber, formatAddress, formatAmountInDollars } from '@/helpers/text-helper.js';
import { getDamageDisplayContent, getLocationAnswer } from '@/helpers/damage-review-content-generator.js';
import {
toTitleCase,
toDisplayPhoneNumber,
formatAddress,
formatAmountInDollars,
} from '@/helpers/text-helper.js';
import {
getDamageDisplayContent,
getLocationAnswer,
} from '@/helpers/damage-review-content-generator.js';
import damageLocationsSelected from '@/constants/damage-locations-selected.js';
const VERIFYING_COVERAGE = 'Verifying coverage';
@ -132,7 +147,7 @@ export default {
siteFooter,
contactDetailsDrawer,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -155,41 +170,66 @@ export default {
vehicle: 'VehicleSubTitle',
damage: 'DamageSubTitle',
shop: 'PreferredShopSubTitle',
contactInfo: 'ContactDetailsSubTitle'
contactInfo: 'ContactDetailsSubTitle',
},
damageLocations: 'DamageLocationsWidget',
orderDetails: 'OrderDetailsContent',
footer: 'SiteFooterWidget'
footer: 'SiteFooterWidget',
},
companyName,
customValueMap: {
glassShop: companyName
}
glassShop: companyName,
},
};
},
computed: {
subHeaderTitle() {
return this.getCmsContent(this.widget.siteSubHeader, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT);
return this.getCmsContent(
this.widget.siteSubHeader,
widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT
);
},
subHeaderBodyOne() {
return this.getCmsContent(this.widget.siteSubHeader, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT);
return this.getCmsContent(
this.widget.siteSubHeader,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
);
},
subHeaderBodyTwo() {
const cmsContent = this.getCmsContent(this.widget.siteSubHeader, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2);
const cmsContent = this.getCmsContent(
this.widget.siteSubHeader,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2
);
return getStringWithCustomValues(cmsContent, this.customValueMap);
},
serviceSummaryText() {
return this.getCmsContent(this.widget.serviceSummary, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
return this.getCmsContent(
this.widget.serviceSummary,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
orderDetailsTitle() {
return this.getCmsContent(this.widget.orderDetails, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT);
return this.getCmsContent(
this.widget.orderDetails,
widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT
);
},
orderDetailsBody() {
const orderDetailsBodyText = this.getCmsContent(this.widget.orderDetails, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT);
return processIfStatements(orderDetailsBodyText, 'custom', this.getCustomValueFromString);
const orderDetailsBodyText = this.getCmsContent(
this.widget.orderDetails,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
);
return processIfStatements(
orderDetailsBodyText,
'custom',
this.getCustomValueFromString
);
},
forwardButtonText() {
return this.getCmsContent(this.widget.footer, widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT);
return this.getCmsContent(
this.widget.footer,
widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT
);
},
isVerified() {
return useMainStore().order.payment.insuranceCoverage.isVerified;
@ -198,7 +238,9 @@ export default {
return useMainStore().order.currentDeductible;
},
deductibleBoxValue() {
return this.isVerified ? formatAmountInDollars(this.currentDeductible) : VERIFYING_COVERAGE;
return this.isVerified
? formatAmountInDollars(this.currentDeductible)
: VERIFYING_COVERAGE;
},
getVehicleLines() {
const { year, make, model } = useMainStore().order.vehicle;
@ -208,15 +250,27 @@ export default {
return [line];
},
locationAnswers() {
return this.getInputQuestionWidgetAnswersNullSafe(this.widget.damageLocations);
return this.getInputQuestionWidgetAnswersNullSafe(
this.widget.damageLocations
);
},
driverSideDamageAnswers() {
const answerContent = getLocationAnswer(damageLocationsSelected.DRIVER, this.locationAnswers);
return this.getInputQuestionWidgetAnswersNullSafe(answerContent?.SubWidgetName);
const answerContent = getLocationAnswer(
damageLocationsSelected.DRIVER,
this.locationAnswers
);
return this.getInputQuestionWidgetAnswersNullSafe(
answerContent?.SubWidgetName
);
},
passengerSideDamageAnswers() {
const answerContent = getLocationAnswer(damageLocationsSelected.PASSENGER, this.locationAnswers);
return this.getInputQuestionWidgetAnswersNullSafe(answerContent?.SubWidgetName);
const answerContent = getLocationAnswer(
damageLocationsSelected.PASSENGER,
this.locationAnswers
);
return this.getInputQuestionWidgetAnswersNullSafe(
answerContent?.SubWidgetName
);
},
getDamageLines() {
const { glassToReplace, isRepair } = useMainStore().order.damage;
@ -229,76 +283,108 @@ export default {
);
},
getPreferredShopLines() {
const { phoneNumber, address } = useMainStore().order.serviceLocation.provider;
const { phoneNumber, address } =
useMainStore().order.serviceLocation.provider;
const { streetAddress, city, state, zipCode } = address;
const displayAddress = formatAddress(streetAddress, null, city, state, zipCode);
const displayAddress = formatAddress(
streetAddress,
null,
city,
state,
zipCode
);
const displayPhoneNumber = toDisplayPhoneNumber(phoneNumber);
return [toTitleCase(this.companyName ?? ''), displayAddress, displayPhoneNumber];
return [
toTitleCase(this.companyName ?? ''),
displayAddress,
displayPhoneNumber,
];
},
getContactInfoLines() {
const { firstName, lastName, emailAddress, servicePhone } = useMainStore().contactInfo;
const { firstName, lastName, emailAddress, servicePhone } =
useMainStore().contactInfo;
return [
`${firstName} ${lastName}`,
emailAddress ?? '',
toDisplayPhoneNumber(servicePhone)
toDisplayPhoneNumber(servicePhone),
];
}
},
},
methods:
{
setSections() {
const vehicleScenario = useMainStore().isPolicyVehicle
? this.navigationScenarios.EDIT_POLICY_VEHICLE
: this.navigationScenarios.EDIT_VEHICLE;
this.sections = [
this.getSection(this.widget.subheader.vehicle, this.getVehicleLines, () => this.navigate(vehicleScenario)),
// eslint-disable-next-line max-len
this.getSection(this.widget.subheader.damage, this.getDamageLines, () => this.navigate(this.navigationScenarios.EDIT_DAMAGE)),
// eslint-disable-next-line max-len
this.getSection(this.widget.subheader.shop, this.getPreferredShopLines, () => this.navigate(this.navigationScenarios.EDIT_PREFERRED_SHOP)),
// eslint-disable-next-line max-len
this.getSection(this.widget.subheader.contactInfo, this.getContactInfoLines, this.openContactDetailsModal)
];
},
getSection(widgetName, lines, onClick) {
return {
title: this.getCmsContent(widgetName, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
lines,
onClickEdit: onClick
};
},
forwardButtonAction() {
this.navigate(this.navigationScenarios.CLICKED_FORWARD);
},
navigate(scenario) {
this.$router.navigate(scenario, this.$route);
},
getCustomValueFromString(str) {
switch (str) {
case 'deductibleAboveZero':
return this.isVerified && this.currentDeductible !== 0;
case 'zeroDeductible':
return this.isVerified && this.currentDeductible === 0;
case 'verifyingCoverage':
return !this.isVerified;
default:
return null;
}
},
openContactDetailsModal() {
this.$refs.contactDetailsDrawer.openModal();
methods: {
setSections() {
const vehicleScenario = useMainStore().isPolicyVehicle
? this.navigationScenarios.EDIT_POLICY_VEHICLE
: this.navigationScenarios.EDIT_VEHICLE;
this.sections = [
this.getSection(
this.widget.subheader.vehicle,
this.getVehicleLines,
() => this.navigate(vehicleScenario)
),
// eslint-disable-next-line max-len
this.getSection(
this.widget.subheader.damage,
this.getDamageLines,
() => this.navigate(this.navigationScenarios.EDIT_DAMAGE)
),
// eslint-disable-next-line max-len
this.getSection(
this.widget.subheader.shop,
this.getPreferredShopLines,
() =>
this.navigate(
this.navigationScenarios.EDIT_PREFERRED_SHOP
)
),
// eslint-disable-next-line max-len
this.getSection(
this.widget.subheader.contactInfo,
this.getContactInfoLines,
this.openContactDetailsModal
),
];
},
getSection(widgetName, lines, onClick) {
return {
title: this.getCmsContent(
widgetName,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
),
lines,
onClickEdit: onClick,
};
},
forwardButtonAction() {
this.navigate(this.navigationScenarios.CLICKED_FORWARD);
},
navigate(scenario) {
this.$router.navigate(scenario, this.$route);
},
getCustomValueFromString(str) {
switch (str) {
case 'deductibleAboveZero':
return this.isVerified && this.currentDeductible !== 0;
case 'zeroDeductible':
return this.isVerified && this.currentDeductible === 0;
case 'verifyingCoverage':
return !this.isVerified;
default:
return null;
}
}
},
openContactDetailsModal() {
this.$refs.contactDetailsDrawer.openModal();
},
},
};
</script>
<style lang="scss" scoped>
.tpa-submit__title--line-height {
line-height: map-get($spacers, 6);
}
.text-color--darker-gray {
color: $darker-gray
color: $darker-gray;
}
.text-color--black {
@ -307,7 +393,6 @@ export default {
hr {
opacity: 1;
color: $gray-350
color: $gray-350;
}
</style>

View file

@ -4,66 +4,94 @@
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="container-fluid pb-2">
<div class="row mt-2 px-3">
<div class="col">
<alert
v-if="shouldDisplayVehicleChangeAlert"
ref="vehicleChangeAlert"
class="mt-5 mb-0"
cmsWidgetName="VehicleChangeAlert"
alertClass="alert-warning"
:isDismissible="false" />
<vehicleBanner
class="mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<damageLocationQuestion
ref="damageLocation"
v-model="selectedDamageLocations"
cmsWidgetName="DamageLocationQuestion"
groupName="DamageLocationQuestion" />
<windshieldOptions
ref="windshieldOptions"
v-model="selectedWindshieldOptions"
:hasRepairReplaceConflict="hasRepairReplaceConflict"
:hasSplitSingleConflict="hasSplitSingleConflict"
:selectedDamageLocations="selectedDamageLocations"
class="mb-3" />
<alert
v-if="hasRepairReplaceConflict"
class="my-5"
cmsWidgetName="HasReplacementConflict"
alertClass="alert-danger"
:isDismissible="false" />
<sideDoorOptions
v-show="!hasRepairReplaceConflict"
ref="sideDoorOptions"
v-model="sideDoorOptionsData"
cmsWidgetName="SideDoorSideQuestion"
groupName="SideDoorSideQuestion"
:selectedDamageLocations="selectedDamageLocations"
class="mb-1" />
<replaceOptionsQuestion
ref="backGlassOptions"
v-model="selectedRearReplaceOptions"
cmsWidgetName="RearReplaceOptionsQuestion"
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
groupName="BackGlassReplaceOptionsQuestion"
validationRules="replace-options-required" />
<site-footer
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
</div>
<div
class="container-fluid fade-on-route-transition replace-options-question">
<div class="row justify-content-center">
<div class="col-md-6 px-0 px-md-2">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<alert
v-if="shouldDisplayVehicleChangeAlert"
ref="vehicleChangeAlert"
class="mt-5 mb-0"
cmsWidgetName="VehicleChangeAlert"
alertClass="alert-warning"
:isDismissible="false" />
<vehicleBanner
class="mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6">
<damageLocationQuestion
ref="damageLocation"
v-model="selectedDamageLocations"
cmsWidgetName="DamageLocationQuestion"
groupName="DamageLocationQuestion" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<windshieldOptions
ref="windshieldOptions"
v-model="selectedWindshieldOptions"
:hasRepairReplaceConflict="hasRepairReplaceConflict"
:hasSplitSingleConflict="hasSplitSingleConflict"
:selectedDamageLocations="selectedDamageLocations"
class="mb-3" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<alert
v-if="hasRepairReplaceConflict"
class="my-5"
cmsWidgetName="HasReplacementConflict"
alertClass="alert-danger"
:isDismissible="false" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<sideDoorOptions
v-show="!hasRepairReplaceConflict"
ref="sideDoorOptions"
v-model="sideDoorOptionsData"
cmsWidgetName="SideDoorSideQuestion"
groupName="SideDoorSideQuestion"
:selectedDamageLocations="selectedDamageLocations"
class="mb-1" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<replaceOptionsQuestion
ref="backGlassOptions"
v-model="selectedRearReplaceOptions"
cmsWidgetName="RearReplaceOptionsQuestion"
:isAvailable="
isRearWindowDamageLocation &&
!hasRepairReplaceConflict
"
groupName="BackGlassReplaceOptionsQuestion"
validationRules="replace-options-required" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<site-footer
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
@ -95,7 +123,10 @@ import damageLocationsSelected from '@/constants/damage-locations-selected.js';
import { useMainStore } from '@/store';
// DEFINE VALIDATION RULES
defineRule('replace-options-required', required(errorMessages.REPLACE_OPTIONS_REQUIRED));
defineRule(
'replace-options-required',
required(errorMessages.REPLACE_OPTIONS_REQUIRED)
);
export default {
name: 'vehicle-damage',
@ -111,7 +142,7 @@ export default {
replaceOptionsQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert
alert,
},
mixins: [BaseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
@ -119,18 +150,20 @@ export default {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const damageOptionsPromise = store.getDamageOptions(store.order.vehicle.carId);
const damageOptionsPromise = store.getDamageOptions(
store.order.vehicle.carId
);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'damageOptions',
promise: damageOptionsPromise
}
promise: damageOptionsPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -138,13 +171,23 @@ export default {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.damageLocation.initializeComponent(resultMap.damageOptions);
vm.$refs.sideDoorOptions.initializeComponent(
resultMap.damageOptions.driverSideOptions.availableReplacementOptions,
resultMap.damageOptions.passengerSideOptions.availableReplacementOptions
vm.$refs.damageLocation.initializeComponent(
resultMap.damageOptions
);
vm.$refs.sideDoorOptions.initializeComponent(
resultMap.damageOptions.driverSideOptions
.availableReplacementOptions,
resultMap.damageOptions.passengerSideOptions
.availableReplacementOptions
);
vm.$refs.windshieldOptions.initializeComponent(
resultMap.damageOptions.windshieldOptions
.availableReplacementOptions
);
vm.$refs.backGlassOptions.initializeComponent(
resultMap.damageOptions.backGlassOptions
.availableReplacementOptions
);
vm.$refs.windshieldOptions.initializeComponent(resultMap.damageOptions.windshieldOptions.availableReplacementOptions);
vm.$refs.backGlassOptions.initializeComponent(resultMap.damageOptions.backGlassOptions.availableReplacementOptions);
});
},
setup() {
@ -157,77 +200,101 @@ export default {
selectedDamageLocations: this.getDamageLocationsFromStore(),
sideDoorOptionsData: {
selectedDoorSides: this.getDoorSidesFromStore(),
selectedDriverSideReplaceOptions: this.getDriverSideReplaceOptionsFromStore(),
selectedPassengerSideReplaceOptions: this.getPassengerSideReplaceOptionsFromStore()
selectedDriverSideReplaceOptions:
this.getDriverSideReplaceOptionsFromStore(),
selectedPassengerSideReplaceOptions:
this.getPassengerSideReplaceOptionsFromStore(),
},
selectedWindshieldOptions: this.getWindshieldOptionsFromStore(),
selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore()
selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore(),
};
},
computed: {
isWindshieldDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => selectedDamages.toUpperCase() === damageLocationsCms.WINDSHIELD);
return this.selectedDamageLocations.some(
(selectedDamages) =>
selectedDamages.toUpperCase() ===
damageLocationsCms.WINDSHIELD
);
},
isSideDoorDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => selectedDamages.toUpperCase() === damageLocationsCms.SIDEDOOR);
return this.selectedDamageLocations.some(
(selectedDamages) =>
selectedDamages.toUpperCase() ===
damageLocationsCms.SIDEDOOR
);
},
isRearWindowDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => selectedDamages.toUpperCase() === damageLocationsCms.REARWINDOW);
return this.selectedDamageLocations.some(
(selectedDamages) =>
selectedDamages.toUpperCase() ===
damageLocationsCms.REARWINDOW
);
},
isWindshieldRepair() {
return (
this.isWindshieldDamageLocation
&& this.selectedWindshieldOptions.selectedWindshieldDamageType
=== damageLocationsSelected.REPAIR
this.isWindshieldDamageLocation &&
this.selectedWindshieldOptions.selectedWindshieldDamageType ===
damageLocationsSelected.REPAIR
);
},
isDriverSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
return this
.sideDoorOptionsData.selectedDoorSides
.some((selectedDriverSide) => selectedDriverSide.toUpperCase() === damageLocationsCms.DRIVERSIDE);
return this.sideDoorOptionsData.selectedDoorSides.some(
(selectedDriverSide) =>
selectedDriverSide.toUpperCase() ===
damageLocationsCms.DRIVERSIDE
);
},
isPassengerSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
return this
.sideDoorOptionsData.selectedDoorSides
.some((selectedPassengerSide) => selectedPassengerSide.toUpperCase() === damageLocationsCms.PASSENGERSIDE);
return this.sideDoorOptionsData.selectedDoorSides.some(
(selectedPassengerSide) =>
selectedPassengerSide.toUpperCase() ===
damageLocationsCms.PASSENGERSIDE
);
},
hasRepairReplaceConflict() {
return (
this.isWindshieldDamageLocation
&& this.selectedDamageLocations.length > 1
&& this.isWindshieldRepair
this.isWindshieldDamageLocation &&
this.selectedDamageLocations.length > 1 &&
this.isWindshieldRepair
);
},
hasSplitSingleConflict() {
if (
!this.selectedDamageLocations?.includes('Windshield')
|| this.selectedWindshieldOptions.selectedWindshieldDamageType
=== damageLocationsSelected.REPAIR
|| !this.selectedWindshieldOptions.selectedWindshieldReplaceOptions
) return false;
!this.selectedDamageLocations?.includes('Windshield') ||
this.selectedWindshieldOptions.selectedWindshieldDamageType ===
damageLocationsSelected.REPAIR ||
!this.selectedWindshieldOptions.selectedWindshieldReplaceOptions
)
return false;
return (
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some((selectedSingleWindshield) => (
selectedSingleWindshield.toUpperCase()
=== damageLocationsSelected.SINGLE.toUpperCase()
))
&& (this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some((selectedDriverWindshield) => (
selectedDriverWindshield.toUpperCase()
=== damageLocationsSelected.DRIVER.toUpperCase()
))
|| this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some((selectedPassengerWindshield) => (
selectedPassengerWindshield.toUpperCase()
=== damageLocationsSelected.PASSENGER.toUpperCase()
)))
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(
(selectedSingleWindshield) =>
selectedSingleWindshield.toUpperCase() ===
damageLocationsSelected.SINGLE.toUpperCase()
) &&
(this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(
(selectedDriverWindshield) =>
selectedDriverWindshield.toUpperCase() ===
damageLocationsSelected.DRIVER.toUpperCase()
) ||
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(
(selectedPassengerWindshield) =>
selectedPassengerWindshield.toUpperCase() ===
damageLocationsSelected.PASSENGER.toUpperCase()
))
);
},
shouldDisplayVehicleChangeAlert() {
return this.$route.params[this.routerParams.DISPLAY_VEHICLE_CHANGE_ALERT];
}
return this.$route.params[
this.routerParams.DISPLAY_VEHICLE_CHANGE_ALERT
];
},
},
methods: {
arePagePrerequisitesValid() {
@ -240,22 +307,32 @@ export default {
const glassSelections = [];
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => glass.glassLocation === damageLocationsSelected.WINDSHIELD)
|| this.mainStore.order.damage.isRepair
this.mainStore.order.damage.glassToReplace?.some(
(glass) =>
glass.glassLocation ===
damageLocationsSelected.WINDSHIELD
) ||
this.mainStore.order.damage.isRepair
) {
glassSelections.push(damageLocationsSelected.WINDSHIELD);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => (
glass.glassLocation === damageLocationsSelected.DRIVER
|| glass.glassLocation === damageLocationsSelected.PASSENGER
))
this.mainStore.order.damage.glassToReplace?.some(
(glass) =>
glass.glassLocation ===
damageLocationsSelected.DRIVER ||
glass.glassLocation ===
damageLocationsSelected.PASSENGER
)
) {
glassSelections.push(damageLocationsSelected.SIDEDOOR);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => glass.glassLocation === damageLocationsSelected.REAR)
this.mainStore.order.damage.glassToReplace?.some(
(glass) =>
glass.glassLocation === damageLocationsSelected.REAR
)
) {
glassSelections.push(damageLocationsSelected.REARWINDOW);
}
@ -266,43 +343,62 @@ export default {
const windShieldOptions = {
selectedWindshieldDamageType: '',
selectedWindshieldChipCount: null,
selectedWindshieldReplaceOptions: []
selectedWindshieldReplaceOptions: [],
};
if (this.mainStore.order.damage.isRepair === undefined) return windshieldOptions;
if (this.mainStore.order.damage.isRepair === undefined)
return windshieldOptions;
if (this.mainStore.order.damage.isRepair) {
windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPAIR;
windShieldOptions.selectedWindshieldChipCount = this.mainStore.order.damage.numberOfChips;
windShieldOptions.selectedWindshieldDamageType =
damageLocationsSelected.REPAIR;
windShieldOptions.selectedWindshieldChipCount =
this.mainStore.order.damage.numberOfChips;
} else {
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => (
glass.glassLocation === damageLocationsSelected.WINDSHIELD
&& glass.glassName === damageLocationsSelected.SINGLE
))
this.mainStore.order.damage.glassToReplace?.some(
(glass) =>
glass.glassLocation ===
damageLocationsSelected.WINDSHIELD &&
glass.glassName === damageLocationsSelected.SINGLE
)
) {
windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.SINGLE);
windShieldOptions.selectedWindshieldDamageType =
damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(
damageLocationsSelected.SINGLE
);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => (
glass.glassLocation === damageLocationsSelected.WINDSHIELD
&& glass.glassName === damageLocationsSelected.DRIVER
))
this.mainStore.order.damage.glassToReplace?.some(
(glass) =>
glass.glassLocation ===
damageLocationsSelected.WINDSHIELD &&
glass.glassName === damageLocationsSelected.DRIVER
)
) {
windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.DRIVER);
windShieldOptions.selectedWindshieldDamageType =
damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(
damageLocationsSelected.DRIVER
);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => (
glass.glassLocation === damageLocationsSelected.WINDSHIELD
&& glass.glassName === damageLocationsSelected.PASSENGER
))
this.mainStore.order.damage.glassToReplace?.some(
(glass) =>
glass.glassLocation ===
damageLocationsSelected.WINDSHIELD &&
glass.glassName ===
damageLocationsSelected.PASSENGER
)
) {
windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.PASSENGER);
windShieldOptions.selectedWindshieldDamageType =
damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(
damageLocationsSelected.PASSENGER
);
}
}
@ -311,13 +407,20 @@ export default {
getDoorSidesFromStore() {
const doorSides = [];
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => glass.glassLocation === damageLocationsSelected.DRIVER)
this.mainStore.order.damage.glassToReplace?.some(
(glass) =>
glass.glassLocation === damageLocationsSelected.DRIVER
)
) {
doorSides.push(damageLocationsSelected.DRIVERSIDE);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => glass.glassLocation === damageLocationsSelected.PASSENGER)
this.mainStore.order.damage.glassToReplace?.some(
(glass) =>
glass.glassLocation ===
damageLocationsSelected.PASSENGER
)
) {
doorSides.push(damageLocationsSelected.PASSENGERSIDE);
}
@ -347,8 +450,11 @@ export default {
return passengerSideReplaceOptions;
},
getRearReplaceOptionsFromStore() {
const rearReplaceOptions = this.mainStore.order.damage
.glassToReplace?.filter((glass) => glass.glassLocation === damageLocationsSelected.REAR)[0]?.glassName;
const rearReplaceOptions =
this.mainStore.order.damage.glassToReplace?.filter(
(glass) =>
glass.glassLocation === damageLocationsSelected.REAR
)[0]?.glassName;
return rearReplaceOptions;
},
@ -360,7 +466,8 @@ export default {
);
if (this.isWindshieldRepair) {
const supportingItems = await useMainStore().getSupportingItems();
const supportingItems =
await useMainStore().getSupportingItems();
useMainStore().updateSupportingItems(supportingItems.data);
}
@ -372,16 +479,22 @@ export default {
} else if (this.mainStore.order.vehicle.vin) {
// If vin already exists, navigate directly to vin-lookup
const partsOrQuestionsResponse = await this.getPartsOrQuestions();
const partsOrQuestionsResponse =
await this.getPartsOrQuestions();
if (partsOrQuestionsResponse.error) {
// To Do: Need requirement on what to do here
window.console.error('Error on retrieving PartsOrQuestions');
window.console.error(
'Error on retrieving PartsOrQuestions'
);
this.$refs.siteFooter.removeLoader();
return null;
}
// Comes from vehicleQuestionsMixin.navigateForward()
await this.navigateForward(partsOrQuestionsResponse.data.partsOrQuestions, this);
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
@ -394,42 +507,48 @@ export default {
selectedGlassToReplace() {
const selectedGlassToReplace = [];
if (this.isWindshieldDamageLocation && !this.isWindshieldRepair) {
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.forEach((wsItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.WINDSHIELD,
glassName: wsItem
});
});
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.forEach(
(wsItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.WINDSHIELD,
glassName: wsItem,
});
}
);
}
if (this.isDriverSideReplace) {
this.sideDoorOptionsData.selectedDriverSideReplaceOptions.forEach((driverItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.DRIVER,
glassName: driverItem
});
});
this.sideDoorOptionsData.selectedDriverSideReplaceOptions.forEach(
(driverItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.DRIVER,
glassName: driverItem,
});
}
);
}
if (this.isPassengerSideReplace) {
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions.forEach((passengerItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.PASSENGER,
glassName: passengerItem
});
});
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions.forEach(
(passengerItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.PASSENGER,
glassName: passengerItem,
});
}
);
}
if (this.isRearWindowDamageLocation) {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.REAR,
glassName: this.selectedRearReplaceOptions
glassName: this.selectedRearReplaceOptions,
});
}
return selectedGlassToReplace;
}
}
},
},
};
</script>

View file

@ -1,36 +1,32 @@
<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>
<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>
</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>
@ -61,7 +57,7 @@ export default {
SiteHeader,
SiteSubHeader,
VehicleBanner,
VinLookupMethods
VinLookupMethods,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -71,8 +67,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -84,13 +80,13 @@ export default {
},
data() {
return {
selectedVinLookupMethod: null
selectedVinLookupMethod: null,
};
},
computed: {
isForwardActionDisabled() {
return this.selectedVinLookupMethod === null;
}
},
},
methods: {
arePagePrerequisiteValid() {
@ -99,19 +95,28 @@ export default {
forwardButtonAction() {
switch (this.selectedVinLookupMethod) {
case vinLookupMethodSelections.MANUALVIN:
this.$router.navigate(this.navigationScenarios.SELECTED_MANUAL_VIN, this.$route);
this.$router.navigate(
this.navigationScenarios.SELECTED_MANUAL_VIN,
this.$route
);
break;
case vinLookupMethodSelections.LICENSEPLATE:
this.$router.navigate(this.navigationScenarios.SELECTED_LICENSE_PLATE, this.$route);
this.$router.navigate(
this.navigationScenarios.SELECTED_LICENSE_PLATE,
this.$route
);
break;
case vinLookupMethodSelections.HOMEADDRESS:
this.$router.navigate(this.navigationScenarios.SELECTED_HOME_ADDRESS, this.$route);
this.$router.navigate(
this.navigationScenarios.SELECTED_HOME_ADDRESS,
this.$route
);
break;
default:
break;
}
},
resetDependentState() {}
}
resetDependentState() {},
},
};
</script>

View file

@ -1,60 +1,55 @@
<template>
<Form
ref="theForm"
@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
ref="vehicleBanner"
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader
ref="siteSubHeader"
cmsWidgetName="SiteSubHeaderWidget" />
<div class="prevent-squish my-5">
<div class="row">
<div class="col">
<alert
id="vehicle-parts-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>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="isForwardActionDisabled"
@backClicked="navigateBackByVehicleQuestions"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</div>
<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 px-0 px-md-2">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
ref="vehicleBanner"
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader
ref="siteSubHeader"
cmsWidgetName="SiteSubHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<alert
id="vehicle-parts-alert"
class="mt-3 mb-3"
alertClass="alert-warning"
cmsWidgetName="AlertWidget"
:isDismissible="false" />
<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>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="isForwardActionDisabled"
@backClicked="navigateBackByVehicleQuestions"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</div>
@ -89,7 +84,7 @@ export default {
vehicleBanner,
siteSubHeader,
siteFooter,
alert
alert,
},
mixins: [BaseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
@ -99,8 +94,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
@ -109,27 +104,36 @@ export default {
// Glass Part Question dynamic component
Object.keys(vm.$refs)
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
.filter(
(r) =>
r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined
)
.forEach((c) =>
vm.$refs[c][0].initializeComponent({
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget
}));
ColorQuestionWidget:
resultMap.cmsContent.ColorQuestionWidget,
FeatureQuestionWidget:
resultMap.cmsContent.FeatureQuestionWidget,
})
);
});
},
data() {
return {
selectedGlassParts: {},
alertWidgetData: Object,
alreadyPopulatedPartsData: []
alreadyPopulatedPartsData: [],
};
},
computed: {
isForwardActionDisabled() {
return this.selectedGlassPartNumbers?.length !== this.PartsFromApi.partsOrQuestions?.length;
return (
this.selectedGlassPartNumbers?.length !==
this.PartsFromApi.partsOrQuestions?.length
);
},
selectedGlassPartNumbers() {
// Compile all selected parts from the page.
// Compile all selected parts from the page.
const numberArray = [];
// eslint-disable-next-line no-restricted-syntax
for (const glassPart of Object.values(this.selectedGlassParts)) {
@ -152,13 +156,15 @@ export default {
FeatureAnswers: [
{
FeatureAnswerText:
p.description === '' ? p.color : p.description,
PartNumber: p.partNumber
}
]
p.description === ''
? p.color
: p.description,
PartNumber: p.partNumber,
},
],
});
return arr;
}, [])
}, []),
}));
return mappedData;
@ -170,18 +176,20 @@ export default {
RefPrefix() {
return 'partQuestion';
}
},
},
mounted() {
this.LoadInitialPartsData();
},
methods: {
arePagePrerequisitesValid() {
// Check if isRepair is populated and if the pageData we need is here (Parts data)
// Check if isRepair is populated and if the pageData we need is here (Parts data)
return (
useMainStore().damage.isRepair != null
&& useMainStore().pageData(issPageValues.VEHICLE_PARTS)
&& Object.keys(useMainStore().pageData(issPageValues.VEHICLE_PARTS)).length !== 0
useMainStore().damage.isRepair != null &&
useMainStore().pageData(issPageValues.VEHICLE_PARTS) &&
Object.keys(
useMainStore().pageData(issPageValues.VEHICLE_PARTS)
).length !== 0
);
},
async forwardButtonAction() {
@ -189,18 +197,25 @@ export default {
// Match them to the parts from the API.
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(this.PartsFromApi.partsOrQuestions)) {
for (const [key, value] of Object.entries(
this.PartsFromApi.partsOrQuestions
)) {
// eslint-disable-next-line no-restricted-syntax
for (const [partKey, partValue] of Object.entries(value.parts)) {
const currentPart = this.PartsFromApi.partsOrQuestions[key].parts[partKey];
for (const [partKey, partValue] of Object.entries(
value.parts
)) {
const currentPart =
this.PartsFromApi.partsOrQuestions[key].parts[partKey];
const isMatched = this.selectedGlassPartNumbers.some((p) => p === currentPart.partNumber);
const isMatched = this.selectedGlassPartNumbers.some(
(p) => p === currentPart.partNumber
);
if (isMatched) {
matchedParts.push({
glassLocation: value.glassLocation,
glassName: value.glassName,
parts: [currentPart]
parts: [currentPart],
});
}
}
@ -208,7 +223,9 @@ export default {
// If no parts could be matched, throw an error (isForwardActionDisabled is based off of matchedParts)
if (this.isForwardActionDisabled) {
this.$refs.siteFooter.removeLoader();
throw new Error('Could not match any parts to the selected parts');
throw new Error(
'Could not match any parts to the selected parts'
);
}
this.navigateForward(matchedParts, null);
@ -216,23 +233,26 @@ export default {
LoadInitialPartsData() {
const partsData = this.PartsFromApi;
this.alreadyPopulatedPartsData = this.mainStore.lineItems.glassParts === null
? []
: this.mainStore.lineItems.glassParts;
this.alreadyPopulatedPartsData =
this.mainStore.lineItems.glassParts === null
? []
: this.mainStore.lineItems.glassParts;
partsData.partsOrQuestions.map((g) => {
// If the part is already populated, use the value from the store and populate the v-model.
// If the part is already populated, use the value from the store and populate the v-model.
Object.keys(this.alreadyPopulatedPartsData).forEach((key) => {
const { partNumber } = this.alreadyPopulatedPartsData[key];
g.parts.forEach((p) => {
if (p.partNumber === partNumber) {
this.selectedGlassParts[`${g.glassLocation}-${g.glassName}`] = p;
this.selectedGlassParts[
`${g.glassLocation}-${g.glassName}`
] = p;
}
});
});
});
}
}
},
},
};
</script>
<style lang="scss" scoped>

View file

@ -1,77 +1,75 @@
<template>
<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 />
<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>
</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>
@ -106,7 +104,7 @@ export default {
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
vehicleQuestion
vehicleQuestion,
},
mixins: [baseFormMixin],
@ -118,8 +116,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -131,7 +129,7 @@ export default {
},
props: {
cmsWidgetName: String,
validationRules: String
validationRules: String,
},
data() {
const { year, make, model, style } = useMainStore().order.vehicle;
@ -139,13 +137,13 @@ export default {
selectedYear: year,
selectedMake: make,
selectedModel: model,
selectedStyle: style
selectedStyle: style,
};
},
computed: {
displayGeneric() {
return !this.selectedStyle;
}
},
},
watch: {
@ -173,7 +171,7 @@ export default {
selectedStyle(value) {
this.mainStore.updateVehicleStyle(value);
this.mainStore.setVehicle();
}
},
},
mounted() {
this.$refs.vehicleYearQuestion.getNewValues();
@ -198,7 +196,10 @@ export default {
navigateForward() {
this.mainStore.setVehicle().then(() => {
if (this.mainStore.isBailout) {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT, this.$route);
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
return;
}
@ -219,22 +220,22 @@ export default {
},
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

@ -4,38 +4,36 @@
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 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>
</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>
@ -77,12 +75,12 @@ export default {
vehicleBanner,
vinLocationInformation,
vinLookupAlerts,
vinQuestion
vinQuestion,
},
mixins: [baseFormMixin, vehicleQuestionsMixin],
provide() {
return {
vehicleFromLookup: computed(() => this.vehicleFromLookup)
vehicleFromLookup: computed(() => this.vehicleFromLookup),
};
},
async beforeRouteEnter(to, from, next) {
@ -92,8 +90,8 @@ export default {
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -110,20 +108,24 @@ export default {
data() {
const vin = this.getVinFromStore();
return {
activeVehicleLookupAlertType: vin?.length > 0 && !this.hasValidCarId() ? vehicleLookupAlertTypes.NOT_FOUND : null,
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()
vinPopulatedOnPageLoad: vin?.length > 0 && this.hasValidCarId(),
};
},
computed: {
isCarIdDifferentFromTheStore() {
return (
this.vehicleFromLookup !== null && this.hasValidCarId()
&& this.vehicleFromLookup.carId !== this.mainStore.vehicle.carId
this.vehicleFromLookup !== null &&
this.hasValidCarId() &&
this.vehicleFromLookup.carId !== this.mainStore.vehicle.carId
);
},
isTwoIdenticalYMMVehicleFound() {
@ -131,15 +133,15 @@ export default {
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());
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.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}`;
@ -148,15 +150,17 @@ export default {
},
carIdIsValid() {
return this.hasValidCarId();
}
},
},
watch: {
vin() {
this.resetActiveAlert();
this.$refs.siteFooter.enableForwardAction();
this.needToLookupVehicle = true;
this.$refs.siteFooter.updateButtonText(this.getCmsContent('SiteFooterWidget', 'ForwardButtonText'));
}
this.$refs.siteFooter.updateButtonText(
this.getCmsContent('SiteFooterWidget', 'ForwardButtonText')
);
},
},
methods: {
arePagePrerequisiteValid() {
@ -166,7 +170,10 @@ export default {
return this.mainStore.vehicle.vin;
},
hasValidCarId() {
return this.mainStore.vehicle.carId && this.mainStore.vehicle.carId !== '0';
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() {
@ -175,11 +182,16 @@ export default {
this.$refs.siteFooter.enableForwardAction();
if (this.needToLookupVehicle) {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.vin);
const vehicleLookupResponse = await this.lookupVehicleByVin(
this.vin
);
if (vehicleLookupResponse.error) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_FOUND;
this.mainStore.setBailout(bailoutMessage.vehicleNotFound(this.vin));
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
@ -192,21 +204,30 @@ export default {
this.mainStore.resetBailout();
// Add vin bcs the response from the service doesn't contain vin
this.vehicleFromLookup = Object.assign(vehicleLookupResponse.data, { vin: this.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.activeVehicleLookupAlertType =
vehicleLookupAlertTypes.TWO_IDENTICAL_YMM_MATCHED;
this.forwardButtonCarStyle = this.vehicleFromLookup.style;
} else {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_MATCHED;
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.updateButtonText(
`Continue with ${vehicleYearMakeModelStyle}`
);
this.$refs.siteFooter.removeLoader();
this.needToLookupVehicle = false;
@ -216,11 +237,17 @@ export default {
let isSelectedGlassAvailableForVehicle = true;
if (this.isCarIdDifferentFromTheStore) {
isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(this.vehicleFromLookup.carId);
isSelectedGlassAvailableForVehicle =
await isGlassAvailableForCarId(
this.vehicleFromLookup.carId
);
}
// navigate back to vehicle-damage
if (this.isCarIdDifferentFromTheStore && !isSelectedGlassAvailableForVehicle) {
if (
this.isCarIdDifferentFromTheStore &&
!isSelectedGlassAvailableForVehicle
) {
this.mainStore.updateVehicle(this.vehicleFromLookup);
this.mainStore.resetDamageState();
this.$router.navigate(
@ -256,7 +283,10 @@ export default {
}
// Comes from vehicleQuestionsMixin.navigateForward()
await this.navigateForward(partsOrQuestionsResponse.data.partsOrQuestions, this);
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
return null;
},
@ -266,8 +296,8 @@ export default {
} catch (responseError) {
return {
error: {
status: responseError.status
}
status: responseError.status,
},
};
}
},
@ -277,7 +307,7 @@ export default {
resetVehicleFromLookup() {
this.vehicleFromLookup = null;
},
resetDependentState() {}
}
resetDependentState() {},
},
};
</script>

View file

@ -4,172 +4,142 @@
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles welcome">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" />
<div class="container-fluid px-5">
<div class="row mt-5">
<div class="col">
<textboxQuestion
ref="policyNumber"
v-model="welcomePageModel.policyNumber"
inputId="policyNumberField"
cmsWidgetName="PolicyNumberQuestion"
isRequired
disableAutoFill
:isDisabled="isPolicyHolderDisabled"
:validationRules="rules.policyNumber" />
</div>
</div>
<div
class="row mt-4">
<div class="col">
<textboxQuestion
ref="policyZip"
v-model="welcomePageModel.policyZipCode"
inputId="policyZipCode"
cmsWidgetName="PolicyZipQuestion"
isRequired
mask="#####"
:isDisabled="isPolicyZipDisabled"
:validationRules="rules.policyZip" />
</div>
</div>
<div class="row mt-4">
<div class="col">
<textboxQuestion
ref="dateOfLoss"
v-model="welcomePageModel.dateOfLoss"
type="date"
cmsWidgetName="DateOfLossQuestion"
inputId="dateOfLossField"
isRequired
:isDisabled="isDateOfLossDisabled"
disableAutoFill
:max="new Date().toJSON().slice(0, 10)"
:min="'1972-12-01'"
:validationRules="rules.lossDate" />
</div>
</div>
<div class="row px-4">
<div class="col px-0">
<textBlock
cmsWidgetName="DamageDateEstimateWidget"
typeStyle="small"
class="mt-2" />
</div>
</div>
<div class="row mt-4">
<div class="col">
<dropdownQuestion
id="welcomeDropdown"
ref="damageCause"
v-model="welcomePageModel.damageCause"
cmsWidgetName="DamageCauseQuestion"
inputId="damageCauseQuestionField"
:options="DamageCauseOptions"
disableAutoFill
:validationRules="rules.damageOption"
placeHolderText="Select an option" />
</div>
</div>
<div class="row mt-4">
<div class="col">
<textboxQuestion
ref="phoneNumber"
v-model="welcomePageModel.phoneNumber"
inputId="phoneNumberField"
cmsWidgetName="PhoneNumberQuestion"
:validationRules="rules.phoneNumber"
isRequired
:mask="phoneMask"
disableAutoFill />
</div>
</div>
<div class="row mt-4">
<div class="col">
<textboxQuestion
ref="email"
v-model="welcomePageModel.email"
inputId="emailField"
cmsWidgetName="EmailAddressQuestion"
:validationRules="rules.email"
isRequired
disableAutoFill />
</div>
</div>
<div class="row">
<div class="col">
<textboxQuestion
v-if="displayDamageCityQuestion"
ref="damageCity"
v-model="welcomePageModel.damageCity"
class="mt-4"
inputId="damageCityField"
cmsWidgetName="DamageCityQuestion"
isRequired
disableAutoFill
:validationRules="rules.lossCity" />
</div>
</div>
<div class="row">
<div class="col">
<dropdownQuestion
v-if="displayDamageStateQuestion"
id="welcomeDropdown"
ref="state"
v-model="welcomePageModel.damageState"
class="mt-4"
cmsWidgetName="DamageStateQuestion"
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="getStates"
:validationRules="rules.lossState"
isRequired
disableAutoFill
placeHolderText="Select an option" />
</div>
</div>
<div class="row mb-3">
<buttonQuestion
v-if="displayGlassOnlyQuestion"
ref="glassOnlyDamage"
v-model="welcomePageModel.isDamageGlassOnly"
class="px-0 mt-4"
cmsWidgetName="GlassOnlyQuestion"
inputId="isDamageGlassOnly"
:answers="DamageGlassOnlyOptions"
:questionText="DamageGlassOnlyQuestion"
groupName="glassOnlyDamageOption"
buttonTypeString="listButtonHorizontal"
:validationRules="rules.damageOption"
isRequired
isSmallQuestionLabelText
disableAutoFill />
</div>
<div
id="welcomeFooter"
class="row position-sticky top-100">
<div class="col">
<alert
v-if="displayInvalidZipAlert"
ref="alertInvalidZip"
class="my-4"
cmsWidgetName="AlertInvalidZipWidget"
alertClass="alert-danger"
:isDismissible="false" />
<siteFooter
ref="siteFooter"
class="mt-3"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction" />
</div>
<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>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" />
<textboxQuestion
ref="policyNumber"
v-model="welcomePageModel.policyNumber"
inputId="policyNumberField"
cmsWidgetName="PolicyNumberQuestion"
isRequired
disableAutoFill
:isDisabled="isPolicyHolderDisabled"
:validationRules="rules.policyNumber"
class="mb-3" />
<textboxQuestion
ref="policyZip"
v-model="welcomePageModel.policyZipCode"
inputId="policyZipCode"
cmsWidgetName="PolicyZipQuestion"
isRequired
mask="#####"
:isDisabled="isPolicyZipDisabled"
:validationRules="rules.policyZip"
class="mb-3" />
<textboxQuestion
ref="dateOfLoss"
v-model="welcomePageModel.dateOfLoss"
type="date"
cmsWidgetName="DateOfLossQuestion"
inputId="dateOfLossField"
isRequired
:isDisabled="isDateOfLossDisabled"
disableAutoFill
:max="new Date().toJSON().slice(0, 10)"
:min="'1972-12-01'"
:validationRules="rules.lossDate"
class="mb-3" />
<textBlock
cmsWidgetName="DamageDateEstimateWidget"
typeStyle="small"
class="mb-3" />
<dropdownQuestion
id="welcomeDropdown"
ref="damageCause"
v-model="welcomePageModel.damageCause"
cmsWidgetName="DamageCauseQuestion"
inputId="damageCauseQuestionField"
:options="DamageCauseOptions"
disableAutoFill
:validationRules="rules.damageOption"
placeHolderText="Select an option"
class="mb-3" />
<textboxQuestion
ref="phoneNumber"
v-model="welcomePageModel.phoneNumber"
inputId="phoneNumberField"
cmsWidgetName="PhoneNumberQuestion"
:validationRules="rules.phoneNumber"
isRequired
:mask="phoneMask"
disableAutoFill
class="mb-3" />
<textboxQuestion
ref="email"
v-model="welcomePageModel.email"
inputId="emailField"
cmsWidgetName="EmailAddressQuestion"
:validationRules="rules.email"
isRequired
disableAutoFill
class="mb-3" />
<textboxQuestion
v-if="displayDamageCityQuestion"
ref="damageCity"
v-model="welcomePageModel.damageCity"
class="mb-3"
inputId="damageCityField"
cmsWidgetName="DamageCityQuestion"
isRequired
disableAutoFill
:validationRules="rules.lossCity" />
<dropdownQuestion
v-if="displayDamageStateQuestion"
id="welcomeDropdown"
ref="state"
v-model="welcomePageModel.damageState"
class="mb-3"
cmsWidgetName="DamageStateQuestion"
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="getStates"
:validationRules="rules.lossState"
isRequired
disableAutoFill
placeHolderText="Select an option" />
<buttonQuestion
v-if="displayGlassOnlyQuestion"
ref="glassOnlyDamage"
v-model="welcomePageModel.isDamageGlassOnly"
class="px-0 mt-4"
cmsWidgetName="GlassOnlyQuestion"
inputId="isDamageGlassOnly"
:answers="DamageGlassOnlyOptions"
:questionText="DamageGlassOnlyQuestion"
groupName="glassOnlyDamageOption"
buttonTypeString="listButtonHorizontal"
:validationRules="rules.damageOption"
isRequired
isSmallQuestionLabelText
disableAutoFill />
<div id="welcomeFooter" class="row">
<alert
v-if="displayInvalidZipAlert"
ref="alertInvalidZip"
class="my-4"
cmsWidgetName="AlertInvalidZipWidget"
alertClass="alert-danger"
:isDismissible="false" />
<siteFooter
ref="siteFooter"
class="mt-3"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</div>
<!-- Footer image component must have parent (usually container-fluid)
set to display: flex and height 100dvh or height 100% -->
<footerImage />
</div>
</Form>
</template>
@ -185,9 +155,14 @@ import textboxQuestion from '@/digital-components/textbox-question/textbox-quest
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
import textBlock from '@/digital-components/text-block/text-block.vue';
import footerImage from '@/iss-components/site-footer/footer-image/footer-image.vue';
// Supporting files
import { fetchCmsContentForPage, fetchGlobalCmsContent, updateCmsSiteHeader } from '@/helpers/cms-content-helper';
import {
fetchCmsContentForPage,
fetchGlobalCmsContent,
updateCmsSiteHeader,
} from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { required } from '@/helpers/validation-rules';
import errorMessages from '@/constants/error-messages';
@ -201,13 +176,17 @@ import globalMethods from '@/global-methods';
import { endpoints } from '@/constants/endpoints';
// define validation rules
defineRule('damage-option-required', required(errorMessages.DAMAGE_OPTION_REQUIRED));
defineRule(
'damage-option-required',
required(errorMessages.DAMAGE_OPTION_REQUIRED)
);
export default {
name: 'welcome-page',
components: {
siteHeader,
siteSubHeader,
footerImage,
alert,
buttonQuestion,
textboxQuestion,
@ -215,24 +194,25 @@ export default {
siteFooter,
textBlock,
// eslint-disable-next-line vue/no-reserved-component-names
Form
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const cmsGlobalSiteHeaderContentPromise = fetchGlobalCmsContent('iss-siteheader');
const cmsGlobalSiteHeaderContentPromise =
fetchGlobalCmsContent('iss-siteheader');
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
promise: cmsContentPromise,
},
{
resultKey: 'globalSiteHeaderCmsContent',
promise: cmsGlobalSiteHeaderContentPromise
}
promise: cmsGlobalSiteHeaderContentPromise,
},
];
// use resultMap to populate layout content.
@ -264,13 +244,16 @@ export default {
lossState: `${globalRules.DATE_OF_LOSS_STATE_REQUIRED}`,
policyNumber: `${globalRules.POLICY_NUMBER_REQUIRED}|${globalRules.POLICY_NUMBER_FORMAT}`,
policyZip: `${globalRules.POLICY_ZIP_REQUIRED}|${globalRules.POLICY_ZIP_FORMAT}`,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`
}
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
},
};
},
computed: {
DamageCauseOptions() {
const damageCauseAnswers = this.getCmsContent('DamageCauseQuestion', 'Answers');
const damageCauseAnswers = this.getCmsContent(
'DamageCauseQuestion',
'Answers'
);
const damageCauseAnswersObj = {};
if (damageCauseAnswers) {
// eslint-disable-next-line no-restricted-syntax
@ -317,16 +300,18 @@ export default {
},
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
}
},
},
methods: {
async forwardButtonAction() {
await this.mainStore.validateZip({ zip: this.welcomePageModel.policyZipCode })
await this.mainStore
.validateZip({ zip: this.welcomePageModel.policyZipCode })
.then(async (zipInfo) => {
if (zipInfo?.data?.isValid === true) {
this.mainStore.updatePolicyData(this.welcomePageModel);
this.mainStore.order.serviceLocation.zipCodeCtu = zipInfo.data.zipCodeCtu?.toString();
this.mainStore.order.serviceLocation.zipCodeCtu =
zipInfo.data.zipCodeCtu?.toString();
const billToInfo = await this.getBillToInfo(
this.mainStore.issConfig.parentAccountNumber,
@ -334,16 +319,31 @@ export default {
);
if (billToInfo !== null) {
this.mainStore.issConfig.billToAccountNumber = billToInfo.billToAccountNumber;
this.mainStore.issConfig.itacCashBillToNumber = billToInfo.itacCashBillToNumber;
this.mainStore.issConfig.itacFnrBillToNumber = billToInfo.itacFnrBillToNumber;
this.mainStore.issConfig.billToAccountNumber =
billToInfo.billToAccountNumber;
this.mainStore.issConfig.itacCashBillToNumber =
billToInfo.itacCashBillToNumber;
this.mainStore.issConfig.itacFnrBillToNumber =
billToInfo.itacFnrBillToNumber;
}
await this.mainStore.getDuplicateReferrals()
.then(() => {}, () => {})
await this.mainStore
.getDuplicateReferrals()
.then(
() => {},
() => {}
)
.finally(async () => {
if (this.isCoverageEnabled && !this.maxCoverageLookupAttemptsReached) {
await this.mainStore.getCoveragePolicyInfo()?.then(() => {}, () => {});
if (
this.isCoverageEnabled &&
!this.maxCoverageLookupAttemptsReached
) {
await this.mainStore
.getCoveragePolicyInfo()
?.then(
() => {},
() => {}
);
} else {
this.mainStore.order.policy.policyLookupSuccessful = false;
}
@ -355,7 +355,6 @@ export default {
}
});
},
async getBillToInfo(parentAccountNumber, providerNumber) {
try {
const payload = {
@ -363,25 +362,29 @@ export default {
providerNumber: providerNumber.toString(),
billToSelectionCriteria: {
typeOfClaim: 'GLASS ONLY',
lineOfBusiness: 'PERSONAL'
}
lineOfBusiness: 'PERSONAL',
},
};
const response = await globalMethods.callHttpClient({
method: endpoints.GetBillToInfo.method,
endpoint: endpoints.GetBillToInfo.url,
payload
payload,
});
return response.data;
} catch (err) {
console.error(`Error Status Code: ${err.data?.status}: ${err.data?.title}`);
console.error(
`Error Status Code: ${err.data?.status}: ${err.data?.title}`
);
return null;
}
},
navigateForward() {
if (this.mainStore.applicationUser.duplicateOrders?.length > 0 ?? false) {
if (
this.mainStore.applicationUser.duplicateOrders?.length > 0 ??
false
) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_DUPLICATES,
this.$route,
@ -392,7 +395,8 @@ export default {
if (this.mainStore.order.policy.vehicles?.length > 0 ?? false) {
// navigate to policy-vehicles page
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
this.navigationScenarios
.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
this.$route,
{},
{ [routerParams.SAVE_SESSION_SYNCHRONOUS]: true }
@ -400,7 +404,8 @@ export default {
} else {
// navigate to vehicle-selection page (manual entry)
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
this.navigationScenarios
.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
this.$route,
{},
{ [routerParams.SAVE_SESSION_SYNCHRONOUS]: true }
@ -424,19 +429,26 @@ export default {
damageCause: this.mainStore.order.policy.damageCause,
damageState: this.mainStore.order.policy.damageState,
damageCity: this.mainStore.order.policy.damageCity,
isDamageGlassOnly: this.mainStore.order.policy.isDamageGlassOnly,
isDamageGlassOnly:
this.mainStore.order.policy.isDamageGlassOnly,
phoneNumber: this.mainStore.order.customer.phoneNumber,
email: this.mainStore.order.customer.emailAddress,
isPolicyNumberDisabled: this.mainStore.order.policy.isPolicyNumberDisabled
isPolicyNumberDisabled:
this.mainStore.order.policy.isPolicyNumberDisabled,
};
}
}
},
},
};
</script>
<style lang="scss">
.page-container-grouped-styles.welcome {
height: auto;
<style lang="scss" scoped>
form {
height: 100dvh;
.container-fluid {
display: flex;
flex-direction: column;
height: 100%;
}
}
@-moz-document url-prefix() {
// Temporary solution that prevents the Continue button from being hidden in Firefox

View file

@ -1,35 +1,61 @@
// Common/Global Styles
// Use this file for global styles that don't or won't have their own stylesheet
html, body {
height: 100%;
}
body {
font-size: 16px;
background-color: #fff;
color: #4D5151;
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;
}
.prevent-squish {
overflow-x: unset;
}
&.page-container-grouped-styles {
overflow: hidden;
display: flex;
flex-direction: column;
}
// Set max-width on columns to prevent overly-wide
// components on extra wide screens.
.col-md-6 {
max-width: 472px;
@include media-breakpoint-up(xl) {
max-width: 708px;
}
.col {
max-width: 236px;
&.one-list-card-width {
max-width: 472px;
@include media-breakpoint-up(xl) {
max-width: 66.6666666%;
}
}
}
.shop-question {
.col {
max-width: 472px;
@include media-breakpoint-up(xl) {
max-width: 66.6666666%;
margin: 0 auto;
}
}
}
}
.col-xl-4 {
max-width: 472px;
.col {
max-width: 100%;
}
}
//END set max-width on columns
}
.pointer {
cursor: pointer;
}
.container,
.container-fluid {
overflow: hidden;
overflow-x: hidden;
}
.sub-container {
@ -38,7 +64,6 @@ body {
width: 100%;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
}
@ -50,50 +75,12 @@ body {
height: 1px;
overflow: hidden;
}
.pac-container {
z-index: 10000 !important;
}
.page-container-grouped-styles {
@extend .container-fluid, .shadow, .p-0, .position-relative, .make-tall;
}
//Footer modal backdrop adjustments for positioning
.modal-backdrop {
left: 50%;
transform: translateX(-50%);
max-width: 576px;
&.show {
opacity: 0.4;
}
}
// Scroll page when modal isn't open
.fade-on-route-transition {
height: calc(100% - 10px);
overflow: auto;
}
// Prevent scroll when modal is open
&.modal-open {
div.page-container-grouped-styles {
overflow: hidden;
}
.fade-on-route-transition {
}
.modal-open {
.container-fluid {
&.fade-on-route-transition {
overflow: hidden;
height: auto;
}
}
.modal-text {
display: inline;
color: $blue;
cursor: pointer;
border: none;
font-size: 14px;
line-height: 24px;
font-weight: 500;
}
}
}

View file

@ -180,13 +180,12 @@ $spacers: (
//Line Height
//Grid breakpoints
//Bootstrap Grid Breakpoints - Use these breakpoints only
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 838px,
lg: 1074px,
xl: 1416px,
md: 768px,
xl: 1200px,
xxl: 1440px,
);
//Shadow

View file

@ -1,33 +1,38 @@
<template>
<a
v-if="linkType === 'navigation'"
class="navigation-link"
:href="href"
@click="handleClick">
{{ text }}<slot name="after-text"></slot>
v-if="linkType === 'navigation'"
class="navigation-link"
:href="href"
@click="handleClick">
{{ text }}<slot name="after-text"></slot>
</a>
<a
v-else-if="linkType === 'footer'"
class="footer-link"
:href="href"
target="_blank"
@click="handleClick">
{{ text }}<slot name="after-text"></slot>
v-else-if="linkType === 'footer'"
class="footer-link"
:href="href"
target="_blank"
@click="handleClick">
{{ text }}<slot name="after-text"></slot>
</a>
<a
v-else-if="linkType === 'textSmall'"
class="small"
:href="href"
@click="handleClick">
{{ text }}<slot name="after-text"></slot>
v-else-if="linkType === 'textSmall'"
class="small"
:href="href"
@click="handleClick">
{{ text }}<slot name="after-text"></slot>
</a>
<a v-else-if="linkType === 'text'" :href="href" @click="handleClick">
{{ text }}<slot name="after-text"></slot>
</a>
<a
v-else-if="linkType === 'text'"
:href="href"
@click="handleClick">
{{ text }}<slot name="after-text"></slot>
</a>
</template>
v-else-if="linkType === 'newWindowLink'"
class="new-window-link"
:href="href"
target="_blank"
@click="handleClick"
>{{ text }}<slot name="after-text"></slot
></a>
</template>
<script>
export default {
@ -36,8 +41,8 @@ export default {
linkType: String,
text: String,
href: {
type: String
}
type: String,
},
},
emits: ['click-event'],
methods: {
@ -49,13 +54,13 @@ export default {
true
);
this.$emit('click-event');
}
}
},
},
};
</script>
<style lang="scss" scoped>
a {
a {
color: $blue;
text-underline-offset: 5px;
line-height: 2;
@ -63,31 +68,32 @@ export default {
font-weight: 500;
max-width: fit-content;
&:hover {
color: $blue-700;
color: $blue-700;
}
&.small {
line-height: 24px;
&:hover {
color: $blue-700;
}
line-height: 24px;
&:hover {
color: $blue-700;
}
}
&.navigation-link {
color: $black;
line-height: 26px;
white-space: nowrap;
align-items: center;
&.navigation-link,
&.new-window-link {
color: $black;
line-height: 26px;
white-space: nowrap;
align-items: center;
}
&.footer-link {
color: $gray-600;
text-decoration: none;
line-height: 20px;
padding: 0 0 2px 0;
font-weight: 400;
font-size: 0.75rem;
&:hover {
color: $gray-600;
text-decoration: none;
line-height: 20px;
padding: 0 0 2px 0;
text-decoration: underline;
}
font-weight: 400;
font-size: 0.75rem;
&:hover {
padding: 0 0 2px 0;
text-decoration: underline;
}
}
}
</style>
}
</style>