INSR-7752: contact-details page updates, other related data read/write updates

This commit is contained in:
Alex Humphries 2026-02-02 16:53:37 -05:00
parent ebd4bf8714
commit 02281b3230
10 changed files with 201 additions and 132 deletions

View file

@ -53,10 +53,10 @@ export const pageProgressMapper = {
'schedule-page': { 'schedule-page': {
percent: 70 percent: 70
}, },
'contact-details': {
percent: 80
},
'service-packages': { 'service-packages': {
percent: 75
},
'contact-details': {
percent: 85 percent: 85
}, },
'payment-method': { 'payment-method': {

View file

@ -176,7 +176,8 @@ export default {
} }
.optional { .optional {
color: $gray-550; font-weight: $font-weight-normal;
color: $darker-gray;
} }
.character-count { .character-count {

View file

@ -15,81 +15,83 @@
<siteSubHeader <siteSubHeader
id="sub-header" id="sub-header"
ref="siteSubHeader" ref="siteSubHeader"
class="mt-4"
:cmsWidgetName="widget.siteSubHeader" /> :cmsWidgetName="widget.siteSubHeader" />
<textboxQuestion <!-- TO DO: Use MSR appointment information for MSR -->
ref="firstNameQuestion" <alert
v-model="firstName" ref="appointmentInformationAlert"
class="mt-5" isCollapsible
inputId="firstName" alertClass="alert-warning"
:cmsWidgetName="widget.firstNameQuestion" :cmsWidgetName="widget.appointmentInformation" />
isRequired
:validationRules="rules.firstName" />
<textboxQuestion
ref="lastNameQuestion"
v-model="lastName"
class="mt-5"
inputId="lastName"
:cmsWidgetName="widget.lastNameQuestion"
isRequired
:validationRules="rules.lastName" />
<textboxQuestion
ref="emailQuestion"
v-model="emailAddress"
class="mt-5"
inputId="emailAddress"
:cmsWidgetName="widget.emailQuestion"
isRequired
:validationRules="rules.emailAddress" />
<textboxQuestion
ref="phoneNumberQuestion"
v-model="phoneNumber"
class="mt-5"
inputId="phoneNumber"
:cmsWidgetName="widget.phoneNumberQuestion"
isRequired
:mask="phoneMask"
:validationRules="rules.phoneNumber" />
<checkbox <checkbox
ref="requestTextUpdatesCheckbox" v-if="showSameAsPolicyAddressQuestion"
v-model="requestTextUpdates" ref="sameAsPolicyAddressQuestion"
:isChecked="requestTextUpdates" v-model="isSameAsPolicyAddress"
class="mt-3" checkboxName="sameAsPolicyAddress"
checkboxName="requestTextUpdates" buttonID="sameAsPolicyAddress"
buttonID="requestTextUpdates" :checkboxLabel="sameAsPolicyAddressQuestionText" />
:checkboxLabel="requestTextUpdatesCheckboxText" /> <textboxQuestion
ref="addressQuestion"
v-model="address"
inputId="address"
:cmsWidgetName="widget.streetAddressQuestion"
isRequired
:validationRules="'street-address-required'" />
<textboxQuestion
ref="address2Question"
v-model="address2"
inputId="address2"
:cmsWidgetName="widget.apartmentQuestion" />
<textboxQuestion
ref="cityQuestion"
v-model="city"
inputId="city"
:cmsWidgetName="widget.cityQuestion"
isRequired
:validationRules="'city-required'" />
<dropdownQuestion
ref="stateQuestion"
v-model="state"
inputId="state"
:cmsWidgetName="widget.stateQuestion"
:options="states"
isDisabled />
<textboxQuestion
ref="zipCodeQuestion"
v-model="zipCode"
inputId="zipCode"
:cmsWidgetName="widget.zipCodeQuestion"
isDisabled />
<alert
ref="changeZipCodeAlert"
class="zip-alert"
isCollapsible
alertClass="alert-warning"
:startCollapsed="true"
:cmsWidgetName="widget.changeZipCodeAlert" />
<buttonQuestion
ref="vehicleProtectedQuestion"
v-model="isVehicleProtected"
inputId="vehicleProtected"
isRequired
groupName="vehicleProtectedQuestion"
:questionText="vehicleProtectedQuestionText"
:answers="vehicleProtectedQuestionAnswers"
:validationRules="'option-required'"
:buttonTypeString="'listButtonHorizontal'" />
<textBlock
ref="clearanceText"
:cmsWidgetName="widget.clearanceText" />
<textareaQuestion <textareaQuestion
ref="notesQuestion" ref="notesQuestion"
v-model="notesForTechnician" v-model="notesForTechnician"
class="mt-5"
inputId="technicianNotes" inputId="technicianNotes"
:isDisabled="false" :isDisabled="false"
:isRequired="false" :isRequired="false"
:cmsWidgetName="widget.notesQuestion" :cmsWidgetName="widget.notesQuestion"
maxLength="250" maxLength="150"
:inputRows="4" /> :inputRows="4" />
<p
ref="disclaimerText"
class="caption dark-gray mt-5">
{{ textUpdateDisclaimerText }} I also agree to
Safelite's
<textLink
ref="privacyPolicyLink"
class="normal-line-height"
linkType="newWindowLink"
text="Privacy Policy"
href="//www.safelite.com/privacy-center" />
and
<textLink
ref="termsOfUseLink"
class="normal-line-height"
linkType="newWindowLink"
text="Terms of Use"
href="//www.safelite.com/terms-of-use" />.
</p>
<siteFooter <siteFooter
ref="siteFooter" ref="siteFooter"
class="my-5"
:cmsWidgetName="widget.siteFooter" :cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@forwardClicked="forwardButtonAction" @forwardClicked="forwardButtonAction"
@ -107,15 +109,25 @@ import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue'
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue'; import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
import checkbox from '@/ux-components/checkbox/checkbox.vue'; import checkbox from '@/ux-components/checkbox/checkbox.vue';
import textareaQuestion from '@/digital-components/textarea-question/textarea-question.vue'; import textareaQuestion from '@/digital-components/textarea-question/textarea-question.vue';
import textLink from '@/ux-components/text-link/text-link.vue'; import alert from '@/ux-components/alert/alert.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
import textBlock from '@/digital-components/text-block/text-block.vue';
// Supporting files // Supporting files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js';
import { Form } from 'vee-validate'; import { defineRule, Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js'; import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store/index.js'; import { useMainStore } from '@/store/index.js';
import globalRules from '@/constants/global-rules.js';
import MaskaFormattedMasks from '@/constants/maska-masks'; import MaskaFormattedMasks from '@/constants/maska-masks';
import errorMessages from '@/constants/error-messages';
import { required } from '@/helpers/validation-rules';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import widgetFields from '@/constants/cms-widget-fields';
import states from '@/constants/states';
// DEFINE VALIDATION RULES
defineRule('street-address-required', required(errorMessages.STREET_ADDRESS_REQUIRED));
defineRule('city-required', required(errorMessages.CITY_REQUIRED));
export default { export default {
name: 'contact-details', name: 'contact-details',
@ -128,7 +140,10 @@ export default {
siteFooter, siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names // eslint-disable-next-line vue/no-reserved-component-names
Form, Form,
textLink alert,
buttonQuestion,
dropdownQuestion,
textBlock
}, },
mixins: [BaseFormMixin], mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -140,58 +155,70 @@ export default {
}, },
data() { data() {
const { const {
firstName,
lastName,
emailAddress,
servicePhone,
requestTextUpdates,
notesForTechnician notesForTechnician
} = useMainStore().contactInfo; } = useMainStore().contactInfo;
const {
address,
address2,
city,
state,
zipCode,
isVehicleProtected
} = useMainStore().serviceLocation;
return { return {
firstName,
lastName,
emailAddress,
phoneNumber: servicePhone,
requestTextUpdates,
notesForTechnician, notesForTechnician,
address,
address2,
city,
state,
zipCode,
isVehicleProtected: isVehicleProtected ?? '',
isSameAsPolicyAddress: false,
widget: { widget: {
siteHeader: 'SiteHeaderWidget', siteHeader: 'SiteHeaderWidget',
siteSubHeader: 'SiteSubHeaderWidget', siteSubHeader: 'SiteSubHeaderWidget',
firstNameQuestion: 'FirstNameQuestionWidget', appointmentInformation: 'AppointmentInformationAlertWidget',
lastNameQuestion: 'LastNameQuestionWidget', MSRAppointmentInformation: 'MSRAppointmentInformationAlertWidget',
sameAsPolicyAddressQuestion: 'SameAsPolicyAddressQuestionWidget',
streetAddressQuestion: 'StreetAddressQuestionWidget',
apartmentQuestion: 'ApartmentQuestionWidget',
emailQuestion: 'EmailQuestionWidget', emailQuestion: 'EmailQuestionWidget',
phoneNumberQuestion: 'PhoneNumberQuestionWidget', cityQuestion: 'CityQuestionWidget',
requestTextUpdates: 'TextContentWidget', stateQuestion: 'StateQuestionWidget',
zipCodeQuestion: 'ZipCodeQuestionWidget',
changeZipCodeAlert: 'ChangeZipCodeAlertWidget',
vehicleProtectedQuestion: 'VehicleProtectedQuestionWidget',
clearanceText: 'ClearanceTextWidget',
notesQuestion: 'NotesQuestionWidget', notesQuestion: 'NotesQuestionWidget',
disclaimer: 'TextUpdateDisclaimerWidget',
siteFooter: 'SiteFooterWidget' siteFooter: 'SiteFooterWidget'
}, },
rules: { states
firstName: globalRules.FIRST_NAME_REQUIRED,
lastName: globalRules.LAST_NAME_REQUIRED,
emailAddress: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`
}
}; };
}, },
computed: { computed: {
/**
* @returns {string} Returns the CMS text associated with the "get text updates" checkbox.
*/
requestTextUpdatesCheckboxText() {
return `${this.getCmsContent(
this.widget.requestTextUpdates,
'Text'
)}*`;
},
/**
* @returns {string} Returns the CMS text associated with the "get text updates" checkbox.
*/
textUpdateDisclaimerText() {
return `*${this.getCmsContent(this.widget.disclaimer, 'Text')}`;
},
phoneMask() { phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER; return MaskaFormattedMasks.PHONE_NUMBER;
},
showSameAsPolicyAddressQuestion() {
return this.zipCode === useMainStore().customerData.addressQuestions.zipCode;
},
sameAsPolicyAddressQuestionText() {
return this.getCmsContent(this.widget.sameAsPolicyAddressQuestion, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
},
vehicleProtectedQuestionText() {
return this.getCmsContent(this.widget.vehicleProtectedQuestion, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
},
vehicleProtectedQuestionAnswers() {
return this.getCmsContent(this.widget.vehicleProtectedQuestion, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS) || [];
}
},
watch: {
isSameAsPolicyAddress(newValue) {
if (newValue) {
this.copyPolicyAddressToServiceLocation();
} else {
this.clearAddressFields();
}
} }
}, },
methods: { methods: {
@ -208,6 +235,13 @@ export default {
}; };
useMainStore().updateContactInfo(contactInfo); useMainStore().updateContactInfo(contactInfo);
useMainStore().updateServiceLocation({
address: this.address,
address2: this.address2,
city: this.city,
isVehicleProtected: this.isVehicleProtected
});
if (this.requestTextUpdates) { if (this.requestTextUpdates) {
useMainStore().updatePhoneNumbers({ useMainStore().updatePhoneNumbers({
service: this.phoneNumber, service: this.phoneNumber,
@ -221,6 +255,17 @@ export default {
} }
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route); this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
},
copyPolicyAddressToServiceLocation() {
const { addressQuestions } = useMainStore().customerData;
this.address = addressQuestions.streetAddress;
this.address2 = addressQuestions.streetAddress2;
this.city = addressQuestions.city;
},
clearAddressFields() {
this.address = '';
this.address2 = '';
this.city = '';
} }
} }
}; };
@ -235,12 +280,26 @@ export default {
padding-right: .9375rem; padding-right: .9375rem;
} }
} }
.zip-alert {
.dark-gray { :deep(a) {
color: $darker-gray; padding: 0;
border-bottom: 1.5px solid #125b7e;
color: #125b7e;
&:hover, &:focus {
border-color: transparent;
color: $heritage-blue-secondary;
text-decoration: none;
}
}
} }
.textbox-question {
.normal-line-height { :deep(.form-label:has(strong)) {
line-height: normal; font-weight: $font-weight-normal;
color: $darker-gray;
strong {
font-weight: 600;
color: $black;
}
}
} }
</style> </style>

View file

@ -414,6 +414,7 @@ export default {
)); ));
this.navigateWithScenario(navigationScenarios.PRICING_LOOKUP_ERROR); this.navigateWithScenario(navigationScenarios.PRICING_LOOKUP_ERROR);
}); });
console.log('ITAC Pricing Results:', pricingResults);
this.setBaseServiceLineItems(pricingResults); this.setBaseServiceLineItems(pricingResults);
} }
}, },

View file

@ -92,7 +92,7 @@ export default {
return useMainStore().order.serviceLocation.appointmentType; return useMainStore().order.serviceLocation.appointmentType;
}, },
customerInfo() { customerInfo() {
return useMainStore().order.contactInfo; return useMainStore().contactInfo;
} }
}, },
methods: { methods: {

View file

@ -354,9 +354,6 @@ export default {
} }
this.mainStore.saveServiceLocation({ this.mainStore.saveServiceLocation({
address: this.streetAddress,
address2: this.streetAddress2,
city: this.city,
state: this.state, state: this.state,
zipCode: this.zipCode, zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu, zipCodeCtu: this.zipCodeCtu,
@ -399,6 +396,7 @@ export default {
this.zipCode = newZip; this.zipCode = newZip;
const zipCodeData = await getZipCodeData(this.zipCode); const zipCodeData = await getZipCodeData(this.zipCode);
this.city = zipCodeData.city; this.city = zipCodeData.city;
this.state = zipCodeData.state;
this.setCtuForMobile(zipCodeData.zipCodeCtu); this.setCtuForMobile(zipCodeData.zipCodeCtu);
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase); this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
@ -436,6 +434,7 @@ export default {
this.zipContainsMilitaryBase = initialData.zipCodeData.containsMilitaryBase; this.zipContainsMilitaryBase = initialData.zipCodeData.containsMilitaryBase;
this.zipCodeCtu = initialData.zipCodeData.zipCodeCtu; this.zipCodeCtu = initialData.zipCodeData.zipCodeCtu;
this.city = initialData.zipCodeData.city; this.city = initialData.zipCodeData.city;
this.state = initialData.zipCodeData.state;
} }
if (initialData.serviceabilityDetails) { if (initialData.serviceabilityDetails) {
@ -502,6 +501,7 @@ export default {
this.mobileZipError = errorMessages.NO_SERVICE_IN_AREA(toTitleCase(zipCodeData.city)); this.mobileZipError = errorMessages.NO_SERVICE_IN_AREA(toTitleCase(zipCodeData.city));
} else { } else {
this.city = zipCodeData.city; this.city = zipCodeData.city;
this.state = zipCodeData.state;
this.zipCode = this.mobileZipCode; this.zipCode = this.mobileZipCode;
this.setCtuForMobile(zipCodeData.zipCodeCtu); this.setCtuForMobile(zipCodeData.zipCodeCtu);
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase); this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);

View file

@ -112,6 +112,12 @@ export default {
const { feeItems } = store.order.lineItems; const { feeItems } = store.order.lineItems;
console.log('Service Packages - Fetched Line Items:', {
rainDefense: resultMap?.rainDefense,
wipers: resultMap?.wipers,
feeItems
});
const availableLineItems = []; const availableLineItems = [];
if (resultMap?.rainDefense) { if (resultMap?.rainDefense) {

View file

@ -584,7 +584,7 @@ const routingTable = () => [
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.CONTACT_DETAILS destinationIssPageValue: issPageValues.SERVICE_PACKAGES
} }
] ]
}, },
@ -593,7 +593,7 @@ const routingTable = () => [
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.SERVICE_PACKAGES destinationIssPageValue: issPageValues.SCHEDULE_PAGE
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD,

View file

@ -351,9 +351,9 @@ export const useMainStore = defineStore({
}, },
contactInfo: (s) => { contactInfo: (s) => {
const obj = { const obj = {
firstName: s.order.contactInfo.firstName ?? s.order.customer.firstName, firstName: s.order.contactInfo.firstName || s.order.customer.firstName,
lastName: s.order.contactInfo.lastName ?? s.order.customer.lastName, lastName: s.order.contactInfo.lastName || s.order.customer.lastName,
emailAddress: s.order.contactInfo.emailAddress ?? s.order.customer.emailAddress, emailAddress: s.order.contactInfo.emailAddress || s.order.customer.emailAddress,
homePhone: s.order.contactInfo.homePhone, homePhone: s.order.contactInfo.homePhone,
alternativePhone: s.order.contactInfo.alternativePhone, alternativePhone: s.order.contactInfo.alternativePhone,
servicePhone: s.order.contactInfo.servicePhone, servicePhone: s.order.contactInfo.servicePhone,
@ -1816,14 +1816,14 @@ export const useMainStore = defineStore({
this.order.vehicle.registration.lastName = registrationInfo?.lastName; this.order.vehicle.registration.lastName = registrationInfo?.lastName;
}, },
updateServiceLocation(serviceLocationInfo) { updateServiceLocation(serviceLocationInfo) {
this.order.serviceLocation.address = serviceLocationInfo.address; this.order.serviceLocation.address = serviceLocationInfo.address ?? this.order.serviceLocation.address;
this.order.serviceLocation.address2 = serviceLocationInfo.address2; this.order.serviceLocation.address2 = serviceLocationInfo.address2 ?? this.order.serviceLocation.address2;
this.order.serviceLocation.city = serviceLocationInfo.city; this.order.serviceLocation.city = serviceLocationInfo.city ?? this.order.serviceLocation.city;
this.order.serviceLocation.state = serviceLocationInfo.state; this.order.serviceLocation.state = serviceLocationInfo.state ?? this.order.serviceLocation.state;
this.order.serviceLocation.zipCode = serviceLocationInfo.zipCode; this.order.serviceLocation.zipCode = serviceLocationInfo.zipCode ?? this.order.serviceLocation.zipCode;
this.order.serviceLocation.zipCodeCtu = serviceLocationInfo.zipCodeCtu; this.order.serviceLocation.zipCodeCtu = serviceLocationInfo.zipCodeCtu ?? this.order.serviceLocation.zipCodeCtu;
this.order.serviceLocation.appointmentType = serviceLocationInfo.appointmentType; this.order.serviceLocation.appointmentType = serviceLocationInfo.appointmentType ?? this.order.serviceLocation.appointmentType;
this.order.serviceLocation.isVehicleProtected = serviceLocationInfo.isVehicleProtected; this.order.serviceLocation.isVehicleProtected = serviceLocationInfo.isVehicleProtected ?? this.order.serviceLocation.isVehicleProtected;
this.order.serviceLocation.provider = { this.order.serviceLocation.provider = {
providerNumber: serviceLocationInfo.provider?.providerNumber, providerNumber: serviceLocationInfo.provider?.providerNumber,

View file

@ -65,7 +65,7 @@
<div <div
v-show="alertCopy" v-show="alertCopy"
:id="collapseId" :id="collapseId"
class="alert-body"> class="alert-body show">
<template <template
v-for="paragraph in splitAlertCopyForParagraphTag" v-for="paragraph in splitAlertCopyForParagraphTag"
:key="paragraph"> :key="paragraph">
@ -186,7 +186,9 @@ export default {
mounted() { mounted() {
this.ensureAlertIsInViewPort(); this.ensureAlertIsInViewPort();
if (this.isCollapsible) { if (this.isCollapsible) {
this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId)); this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId), {
toggle: this.startCollapsed
});
} }
}, },
methods: { methods: {