Merge pull request #1042 from Safelite/feature/humphries/INSR-7752

INSR-7752: Contact Details parity updates
This commit is contained in:
AHumphriesSL 2026-02-05 15:27:21 -05:00 committed by GitHub
commit f883cd9cc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 612 additions and 599 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

@ -79,7 +79,7 @@
</fieldset> </fieldset>
</div> </div>
<div <div
v-if="!suppressError" v-if="!suppressError && errorMessage"
class="row form-test-error mt-1"> class="row form-test-error mt-1">
<error-message :name="formatString(groupName)"></error-message> <error-message :name="formatString(groupName)"></error-message>
</div> </div>

View file

@ -67,7 +67,7 @@ describe('textarea-question.vue', () => {
}, },
mixins: [mockMixin] mixins: [mockMixin]
}); });
const expected = `${maxLength}/${maxLength}`; const expected = `${maxLength} characters allowed`;
// Act // Act
// Assert // Assert

View file

@ -18,7 +18,7 @@
:id="inputId" :id="inputId"
:ref="inputId" :ref="inputId"
v-model.trim="value" v-model.trim="value"
class="form-control" class="textarea-input"
:name="inputId" :name="inputId"
:rows="[inputRows ?? 10]" :rows="[inputRows ?? 10]"
:placeholder="placeholderText" :placeholder="placeholderText"
@ -32,8 +32,15 @@
@drop="trimOnPaste"> @drop="trimOnPaste">
</textarea> </textarea>
</div> </div>
<p class="character-count margin-top-8"> <p
{{ maxLength - characterCount }}/{{ maxLength }} characters remaining v-if="characterCount == 0"
class="character-count">
{{ maxLength }} characters allowed
</p>
<p
v-else
class="character-count">
{{ maxLength - characterCount }} characters left
</p> </p>
<div <div
v-show="errorMessage" v-show="errorMessage"
@ -103,7 +110,7 @@ export default {
computed: { computed: {
/** /**
* @summary Returns the number of characters in the textarea field. * @summary Returns the number of characters in the textarea field.
* @returns {number} * @returns {number} The number of characters.
*/ */
characterCount() { characterCount() {
return this?.modelValue?.length ?? 0; return this?.modelValue?.length ?? 0;
@ -155,19 +162,18 @@ export default {
font-weight: 500; font-weight: 500;
} }
.form-control { .textarea-input {
max-height: 30rem; padding: .625rem;
border: $border-input; resize: none;
border-radius: $border-radius; margin-bottom: .625rem;
padding: 12px 16px; width: 100%;
letter-spacing: inherit; letter-spacing: inherit;
box-shadow: $box-shadow-input; font-family: inherit;
font-size: inherit;
line-height: 1.375rem;
&::placeholder { &::placeholder {
color: $gray-500; color: $gray-500;
} }
&:focus {
border: $border-input-focus;
}
&:disabled, &:disabled,
&.disabled { &.disabled {
background-color: $gray-100; background-color: $gray-100;
@ -176,16 +182,13 @@ export default {
} }
.optional { .optional {
color: $gray-550; font-weight: $font-weight-normal;
color: $darker-gray;
} }
.character-count { .character-count {
color: $gray-600; color: #4d4e53;
font-size: 12px; font-size: 1rem;
margin-bottom: 0px; line-height: 1.375rem;
}
.margin-top-8 {
margin-top: 8px;
} }
</style> </style>

View file

@ -32,65 +32,175 @@ describe('contactDetails.vue', () => {
// Assert // Assert
expect(siteSubHeader.exists()).toBe(true); expect(siteSubHeader.exists()).toBe(true);
}); });
test('Should render first name question subcomponent', () => { test('Should render appointment information alert', () => {
// Arrange // Arrange
const wrapper = shallowMount(contactDetails, getMountOptions()); const wrapper = shallowMount(contactDetails, getMountOptions());
// Act // Act
const firstNameQuestion = wrapper.findComponent({ ref: 'firstNameQuestion' }); const appointmentInformationAlert = wrapper.findComponent({ ref: 'appointmentInformationAlert' });
// Assert // Assert
expect(firstNameQuestion.exists()).toBe(true); expect(appointmentInformationAlert.exists()).toBe(true);
}); });
test('Should render last name question subcomponent', () => { test('Should render same as policy address question subcomponent if service zip is same as customer zip', async () => {
// Arrange // Arrange
const wrapper = shallowMount(contactDetails, getMountOptions()); const mountOptions = getMountOptions();
// Act const customerStreetAddress = getRandomString(10, 50);
const lastNameQuestion = wrapper.findComponent({ ref: 'lastNameQuestion' }); const customerStreetAddress2 = getRandomString(0, 50);
const customerCity = getRandomString(4, 20);
// Assert const customerState = getRandomString(2, 2);
expect(lastNameQuestion.exists()).toBe(true); const serviceStreetAddress = getRandomString(10, 50);
}); const serviceStreetAddress2 = getRandomString(0, 50);
test('Should render email question subcomponent', () => { const serviceCity = getRandomString(4, 20);
// Arrange const serviceState = getRandomString(2, 2);
const wrapper = shallowMount(contactDetails, getMountOptions()); const zipCode = getRandomInt(10000, 99999).toString();
const mainInitialState = {
// Act order: {
const emailQuestion = wrapper.findComponent({ ref: 'emailQuestion' }); customer: {
address: {
// Assert streetAddress: customerStreetAddress,
expect(emailQuestion.exists()).toBe(true); streetAddress2: customerStreetAddress2,
}); city: customerCity,
test('Should render phone number question subcomponent', () => { state: customerState,
// Arrange zipCode
const wrapper = shallowMount(contactDetails, getMountOptions()); }
},
// Act serviceLocation: {
const phoneNumberQuestion = wrapper.findComponent({ ref: 'phoneNumberQuestion' }); streetAddress: serviceStreetAddress,
streetAddress2: serviceStreetAddress2,
// Assert city: serviceCity,
expect(phoneNumberQuestion.exists()).toBe(true); state: serviceState,
}); zipCode
test('Should render text updates checkbox subcomponent', () => { }
// Arrange
const checkboxLabel = getRandomString(50, 100);
const mockMixin = {
methods: {
getCmsContent: jest.fn().mockImplementation(() => checkboxLabel),
setCmsContent: jest.fn()
} }
}; };
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
await wrapper.vm.$nextTick();
// Act
const sameAsPolicyAddressQuestion = wrapper.findComponent({ ref: 'sameAsPolicyAddressQuestion' });
// Assert
expect(sameAsPolicyAddressQuestion.exists()).toBe(true);
});
test('Should not render same as policy address question subcomponent if service zip is different from customer zip', () => {
// Arrange
const mountOptions = getMountOptions(); const mountOptions = getMountOptions();
mountOptions.mixins = [mockMixin];
const firstName = getRandomString(4, 15);
const lastName = getRandomString(4, 15);
const emailAddress = getRandomString(10, 20);
const servicePhone = getRandomInt(1000000000, 9999999999).toString();
const testZipCode = getRandomInt(10000, 99999);
const customerZipCode = testZipCode.toString();
const serviceZipCode = (testZipCode + 1).toString();
const mainInitialState = {
order: {
customer: {
firstName,
lastName,
emailAddress,
address: {
zipCode: customerZipCode
}
},
contactInfo: {
servicePhone
},
serviceLocation: {
zipCode: serviceZipCode
}
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions); const wrapper = shallowMount(contactDetails, mountOptions);
// Act // Act
const textUpdatesCheckbox = wrapper.findComponent({ ref: 'requestTextUpdatesCheckbox' }); const sameAsPolicyAddressQuestion = wrapper.findComponent({ ref: 'sameAsPolicyAddressQuestion' });
// Assert // Assert
expect(textUpdatesCheckbox.exists()).toBe(true); expect(sameAsPolicyAddressQuestion.exists()).toBe(false);
expect(wrapper.vm.requestTextUpdatesCheckboxText).toBe(`${checkboxLabel}*`); });
test('Should render address question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
// Act
const addressQuestion = wrapper.findComponent({ ref: 'addressQuestion' });
// Assert
expect(addressQuestion.exists()).toBe(true);
});
test('Should render apartment question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
// Act
const address2Question = wrapper.findComponent({ ref: 'address2Question' });
// Assert
expect(address2Question.exists()).toBe(true);
});
test('Should render city question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
// Act
const cityQuestion = wrapper.findComponent({ ref: 'cityQuestion' });
// Assert
expect(cityQuestion.exists()).toBe(true);
});
test('Should render state question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
// Act
const stateQuestion = wrapper.findComponent({ ref: 'stateQuestion' });
// Assert
expect(stateQuestion.exists()).toBe(true);
});
test('Should render zip code question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
// Act
const zipCodeQuestion = wrapper.findComponent({ ref: 'zipCodeQuestion' });
// Assert
expect(zipCodeQuestion.exists()).toBe(true);
});
test('Should render change zip code alert', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
// Act
const changeZipCodeAlert = wrapper.findComponent({ ref: 'changeZipCodeAlert' });
// Assert
expect(changeZipCodeAlert.exists()).toBe(true);
});
test('Should render vehicle protected question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
// Act
const vehicleProtectedQuestion = wrapper.findComponent({ ref: 'vehicleProtectedQuestion' });
// Assert
expect(vehicleProtectedQuestion.exists()).toBe(true);
}); });
test('Should render technician notes textarea question subcomponent', () => { test('Should render technician notes textarea question subcomponent', () => {
// Arrange // Arrange
@ -102,46 +212,15 @@ describe('contactDetails.vue', () => {
// Assert // Assert
expect(notesQuestion.exists()).toBe(true); expect(notesQuestion.exists()).toBe(true);
}); });
test('Should render disclaimer text', () => { test('Should render clearance text subcomponent', () => {
// Arrange
const disclaimerText = getRandomString(50, 100);
const mockMixin = {
methods: {
getCmsContent: jest.fn().mockImplementation(() => disclaimerText),
setCmsContent: jest.fn()
}
};
const mountOptions = getMountOptions();
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(contactDetails, mountOptions);
const expectedDisclaimerText = `*${disclaimerText} I also agree to Safelite's`;
// Act
const componentText = wrapper.text();
// Assert
expect(wrapper.vm.textUpdateDisclaimerText).toBe(`*${disclaimerText}`);
expect(componentText).toContain(expectedDisclaimerText);
});
test('Should render privacy policy link', () => {
// Arrange // Arrange
const wrapper = shallowMount(contactDetails, getMountOptions()); const wrapper = shallowMount(contactDetails, getMountOptions());
// Act // Act
const privacyPolicyLink = wrapper.findComponent({ ref: 'privacyPolicyLink' }); const clearanceText = wrapper.findComponent({ ref: 'clearanceText' });
// Assert // Assert
expect(privacyPolicyLink.exists()).toBe(true); expect(clearanceText.exists()).toBe(true);
});
test('Should render terms of use link', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
// Act
const termsOfUseLink = wrapper.findComponent({ ref: 'termsOfUseLink' });
// Assert
expect(termsOfUseLink.exists()).toBe(true);
}); });
test('Should render site footer', () => { test('Should render site footer', () => {
// Arrange // Arrange
@ -153,82 +232,48 @@ describe('contactDetails.vue', () => {
// Assert // Assert
expect(footer.exists()).toBe(true); expect(footer.exists()).toBe(true);
}); });
test('Mocked store with no contact info yields expected data', () => { test('Mocked store yields expected data', () => {
// Arrange // Arrange
const mountOptions = getMountOptions(); const mountOptions = getMountOptions();
const firstName = getRandomString(4, 15); const address = getRandomString(10, 50);
const lastName = getRandomString(4, 15); const address2 = getRandomString(0, 50);
const emailAddress = getRandomString(10, 20); const city = getRandomString(4, 20);
const servicePhone = getRandomInt(1000000000, 9999999999).toString(); const state = getRandomString(2, 2);
const zipCode = getRandomInt(10000, 99999).toString();
const notesForTechnician = getRandomString(1, 100);
const isVehicleProtected = getRandomBoolean();
const mainInitialState = { const mainInitialState = {
order: { order: {
customer: { serviceLocation: {
firstName, address,
lastName, address2,
emailAddress city,
state,
zipCode,
isVehicleProtected
}, },
contactInfo: { contactInfo: {
servicePhone notesForTechnician
} }
} }
}; };
mountOptions.global = { mountOptions.global.plugins = [createTestingPinia({
plugins: [createTestingPinia({ initialState: {
initialState: { main: mainInitialState
main: mainInitialState }
} })];
})]
};
const wrapper = shallowMount(contactDetails, mountOptions); const wrapper = shallowMount(contactDetails, mountOptions);
// Assert // Assert
expect(wrapper.vm.firstName).toBe(firstName); expect(wrapper.vm.address).toBe(address);
expect(wrapper.vm.lastName).toBe(lastName); expect(wrapper.vm.address2).toBe(address2);
expect(wrapper.vm.emailAddress).toBe(emailAddress); expect(wrapper.vm.city).toBe(city);
expect(wrapper.vm.phoneNumber).toBe(servicePhone); expect(wrapper.vm.state).toBe(state);
}); expect(wrapper.vm.zipCode).toBe(zipCode);
test('Mock store with contact info yields expected data', () => { expect(wrapper.vm.isVehicleProtected).toBe(isVehicleProtected);
// Arrange expect(wrapper.vm.notesForTechnician).toBe(notesForTechnician);
const customer = {
firstName: getRandomString(4, 15),
lastName: getRandomString(4, 15),
emailAddress: getRandomString(10, 20),
phoneNumber: getRandomInt(1000000000, 9999999999)
};
const contactInfo = {
firstName: getRandomString(4, 15),
lastName: getRandomString(4, 15),
emailAddress: getRandomString(10, 20),
servicePhone: getRandomInt(1000000000, 9999999999),
requestTextUpdates: getRandomBoolean(),
notesForTechnician: getRandomString(50, 100)
};
const mainInitialState = {
order: {
customer,
contactInfo
}
};
const mountOptions = getMountOptions();
mountOptions.global = {
plugins: [createTestingPinia({
initialState: {
main: mainInitialState
}
})]
};
const wrapper = shallowMount(contactDetails, mountOptions);
// Assert
expect(wrapper.vm.firstName).toBe(contactInfo.firstName);
expect(wrapper.vm.lastName).toBe(contactInfo.lastName);
expect(wrapper.vm.emailAddress).toBe(contactInfo.emailAddress);
expect(wrapper.vm.phoneNumber).toBe(contactInfo.servicePhone);
expect(wrapper.vm.requestTextUpdates).toBe(contactInfo.requestTextUpdates);
expect(wrapper.vm.notesForTechnician).toBe(contactInfo.notesForTechnician);
}); });
}); });
@ -250,7 +295,7 @@ describe('contactDetails.vue', () => {
expect(wrapper.vm.$router.navigateWithSpinner) expect(wrapper.vm.$router.navigateWithSpinner)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_BACK, undefined); .toHaveBeenCalledWith(navigationScenarios.CLICKED_BACK, undefined);
}); });
test('Forward button clicked triggers appropriate navigation when safelite shop', () => { test('Forward button clicked triggers appropriate navigation', () => {
// Arrange // Arrange
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
router: { router: {
@ -276,37 +321,44 @@ describe('contactDetails.vue', () => {
// Assert // Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
expect(wrapper.vm.$router.navigate) expect(wrapper.vm.$router.navigate)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP, undefined); .toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD, undefined);
}); });
test('Forward button clicked triggers appropriate navigation when TPA shop', () => { test('Forward button click updates service location info', () => {
// Arrange // Arrange
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
router: { router: {
navigate: jest.fn() navigate: jest.fn()
}, }
navigationScenarios
}); });
const mainInitialState = {
order: {
serviceLocation: { IsSafeliteProvider: false }
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions); const wrapper = shallowMount(contactDetails, mountOptions);
const address = getRandomString(10, 50);
const address2 = getRandomString(0, 50);
const city = getRandomString(4, 20);
const state = getRandomString(2, 2);
const zipCode = getRandomInt(10000, 99999).toString();
const isVehicleProtected = getRandomBoolean().toString();
wrapper.setData({
address,
address2,
city,
state,
zipCode,
isVehicleProtected
});
// Act // Act
wrapper.vm.forwardButtonAction(); wrapper.vm.forwardButtonAction();
// Assert // Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); expect(useMainStore().updateServiceLocation).toHaveBeenCalledWith({
expect(wrapper.vm.$router.navigate) address,
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP, undefined); address2,
city,
isVehicleProtected
});
}); });
test('Forward button click updates contact info', () => {
test('Forward button click updates notes for technician', () => {
// Arrange // Arrange
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
router: { router: {
@ -314,18 +366,8 @@ describe('contactDetails.vue', () => {
} }
}); });
const wrapper = shallowMount(contactDetails, mountOptions); const wrapper = shallowMount(contactDetails, mountOptions);
const firstName = getRandomString(4, 15);
const lastName = getRandomString(4, 15);
const emailAddress = getRandomString(10, 20);
const phoneNumber = getRandomInt(1000000000, 9999999999).toString();
const requestTextUpdates = getRandomBoolean();
const notesForTechnician = getRandomString(1, 100); const notesForTechnician = getRandomString(1, 100);
wrapper.setData({ wrapper.setData({
firstName,
lastName,
emailAddress,
phoneNumber,
requestTextUpdates,
notesForTechnician notesForTechnician
}); });
@ -334,146 +376,8 @@ describe('contactDetails.vue', () => {
// Assert // Assert
expect(useMainStore().updateContactInfo).toHaveBeenCalledWith({ expect(useMainStore().updateContactInfo).toHaveBeenCalledWith({
firstName,
lastName,
emailAddress,
requestTextUpdates,
notesForTechnician notesForTechnician
}); });
}); });
test('Forward button click updates phone numbers request text updates', () => {
// Arrange
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const wrapper = shallowMount(contactDetails, mountOptions);
const phoneNumber = getRandomInt(1000000000, 9999999999).toString();
const requestTextUpdates = true;
wrapper.setData({
phoneNumber,
requestTextUpdates
});
// Act
wrapper.vm.forwardButtonAction();
// Assert
expect(useMainStore().updatePhoneNumbers).toHaveBeenCalledWith({
service: phoneNumber,
alternative: phoneNumber
});
});
test('Forward button click updates phone numbers dot not request text updates', () => {
// Arrange
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const wrapper = shallowMount(contactDetails, mountOptions);
const phoneNumber = getRandomInt(1000000000, 9999999999).toString();
const requestTextUpdates = false;
wrapper.setData({
phoneNumber,
requestTextUpdates
});
// Act
wrapper.vm.forwardButtonAction();
// Assert
expect(useMainStore().updatePhoneNumbers).toHaveBeenCalledWith({
home: phoneNumber,
service: phoneNumber
});
});
});
test('Mocked store with no contact info yields expected data', () => {
// Arrange
const mountOptions = getMountOptions();
const firstName = getRandomString(4, 15);
const lastName = getRandomString(4, 15);
const emailAddress = getRandomString(10, 20);
const servicePhone = getRandomInt(1000000000, 9999999999);
const mainInitialState = {
order: {
customer: {
firstName,
lastName,
emailAddress
},
contactInfo: {
firstName: null,
lastName: null,
emailAddress: null,
servicePhone,
requestTextUpdates: null,
notesForTechnician: null
}
}
};
mountOptions.global = {
plugins: [createTestingPinia({
initialState: {
main: mainInitialState
}
})]
};
const wrapper = shallowMount(contactDetails, mountOptions);
// Assert
expect(wrapper.vm.firstName).toBe(firstName);
expect(wrapper.vm.lastName).toBe(lastName);
expect(wrapper.vm.emailAddress).toBe(emailAddress);
expect(wrapper.vm.phoneNumber).toBe(servicePhone);
});
test('Mock store with contact info yields expected data', () => {
// Arrange
const customer = {
firstName: getRandomString(4, 15),
lastName: getRandomString(4, 15),
emailAddress: getRandomString(10, 20),
phoneNumber: getRandomInt(1000000000, 9999999999)
};
const contactInfo = {
firstName: getRandomString(4, 15),
lastName: getRandomString(4, 15),
emailAddress: getRandomString(10, 20),
servicePhone: getRandomInt(1000000000, 9999999999),
requestTextUpdates: getRandomBoolean(),
notesForTechnician: getRandomString(50, 100)
};
const mainInitialState = {
order: {
customer,
contactInfo
}
};
const mountOptions = getMountOptions();
mountOptions.global = {
plugins: [createTestingPinia({
initialState: {
main: mainInitialState
}
})]
};
const wrapper = shallowMount(contactDetails, mountOptions);
// Assert
expect(wrapper.vm.firstName).toBe(contactInfo.firstName);
expect(wrapper.vm.lastName).toBe(contactInfo.lastName);
expect(wrapper.vm.emailAddress).toBe(contactInfo.emailAddress);
expect(wrapper.vm.phoneNumber).toBe(contactInfo.servicePhone);
expect(wrapper.vm.requestTextUpdates).toBe(contactInfo.requestTextUpdates);
expect(wrapper.vm.notesForTechnician).toBe(contactInfo.notesForTechnician);
}); });
}); });

View file

@ -15,81 +15,92 @@
<siteSubHeader <siteSubHeader
id="sub-header" id="sub-header"
ref="siteSubHeader" ref="siteSubHeader"
class="mt-4" class="subheader"
: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" class="form-group same-address-checkbox"
:checkboxLabel="requestTextUpdatesCheckboxText" /> :checkboxLabel="sameAsPolicyAddressQuestionText" />
<textboxQuestion
ref="addressQuestion"
v-model="address"
inputId="address"
class="form-group"
:cmsWidgetName="widget.streetAddressQuestion"
isRequired
:validationRules="'street-address-required'" />
<textboxQuestion
ref="address2Question"
v-model="address2"
inputId="address2"
class="form-group"
:cmsWidgetName="widget.apartmentQuestion" />
<textboxQuestion
ref="cityQuestion"
v-model="city"
inputId="city"
class="form-group"
:cmsWidgetName="widget.cityQuestion"
isRequired
:validationRules="'city-required'" />
<dropdownQuestion
ref="stateQuestion"
v-model="state"
inputId="state"
class="form-group"
:cmsWidgetName="widget.stateQuestion"
:options="states"
isDisabled />
<textboxQuestion
ref="zipCodeQuestion"
v-model="zipCode"
inputId="zipCode"
class="form-group"
: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"
class="form-group"
isRequired
groupName="vehicleProtectedQuestion"
:questionText="vehicleProtectedQuestionText"
:answers="vehicleProtectedQuestionAnswers"
:buttonTypeString="'listButtonHorizontal'" />
<textBlock
ref="clearanceText"
class="clearance-text"
:cmsWidgetName="widget.clearanceText" />
<textareaQuestion <textareaQuestion
ref="notesQuestion" ref="notesQuestion"
v-model="notesForTechnician" v-model="notesForTechnician"
class="mt-5"
inputId="technicianNotes" inputId="technicianNotes"
class="form-group"
: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 +118,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';
import buttonQuestion from '@/digital-components/button-question/button-question.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 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 +149,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 +164,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: {
@ -200,34 +236,29 @@ export default {
*/ */
forwardButtonAction() { forwardButtonAction() {
const contactInfo = { const contactInfo = {
firstName: this.firstName,
lastName: this.lastName,
emailAddress: this.emailAddress,
requestTextUpdates: this.requestTextUpdates,
notesForTechnician: this.notesForTechnician notesForTechnician: this.notesForTechnician
}; };
useMainStore().updateContactInfo(contactInfo); useMainStore().updateContactInfo(contactInfo);
if (this.requestTextUpdates) { useMainStore().updateServiceLocation({
useMainStore().updatePhoneNumbers({ address: this.address,
service: this.phoneNumber, address2: this.address2,
alternative: this.phoneNumber city: this.city,
}); isVehicleProtected: this.isVehicleProtected || 'false'
} else { });
useMainStore().updatePhoneNumbers({
home: this.phoneNumber,
service: this.phoneNumber
});
}
const scenario = this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
useMainStore().order.serviceLocation.IsSafeliteProvider },
=== false copyPolicyAddressToServiceLocation() {
? this.navigationScenarios const { addressQuestions } = useMainStore().customerData;
.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP this.address = addressQuestions.streetAddress;
: this.navigationScenarios this.address2 = addressQuestions.streetAddress2;
.CLICKED_FORWARD_WITH_SAFELITE_SHOP; this.city = addressQuestions.city;
this.$router.navigate(scenario, this.$route); },
clearAddressFields() {
this.address = '';
this.address2 = '';
this.city = '';
} }
} }
}; };
@ -242,12 +273,48 @@ 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;
}
}
}
.subheader {
margin: 1.25rem 0;
}
.form-group {
margin-bottom: 1.25rem;
}
.same-address-checkbox {
:deep(.form-check-input) {
&:checked + label p,
& + label p {
font-size: 1rem;
font-weight: $font-weight-normal;
color: $darker-gray;
}
}
}
.clearance-text {
font-size: .875rem;
line-height: 1.25rem;
margin-top: 0rem;
margin-bottom: .3125rem;
} }
</style> </style>

View file

@ -79,12 +79,14 @@ const initialStore = {
isInsurance: true, isInsurance: true,
paymentMethod: paymentMethods.PAY_AT_TIME_OF_SERVICE paymentMethod: paymentMethods.PAY_AT_TIME_OF_SERVICE
}, },
contactInfo: { customer: {
firstName: 'Test', firstName: 'Test',
lastName: 'Test', lastName: 'Test',
servicePhone: '111-111-1111',
emailAddress: 'test@email.com' emailAddress: 'test@email.com'
}, },
contactInfo: {
servicePhone: '123-456-7890'
},
damage: { damage: {
isRepair: false isRepair: false
}, },
@ -138,7 +140,14 @@ const sessionStorage = {
make: 'Honda', make: 'Honda',
model: 'Civic' model: 'Civic'
}, },
customer: {}, customer: {
firstName: 'Test',
lastName: 'Test',
emailAddress: 'test@email.com'
},
contactInfo: {
servicePhone: '123-456-7890'
},
customerPortalLoginToken: 'token', customerPortalLoginToken: 'token',
currentDeductible: { currentDeductible: {
replace: 100, replace: 100,

View file

@ -373,7 +373,7 @@ export default {
); );
// Contact Info // Contact Info
const { contactInfo } = useMainStore().order; const { contactInfo } = useMainStore();
const contactInfoReqs = !!( const contactInfoReqs = !!(
contactInfo.firstName contactInfo.firstName
&& contactInfo.lastName && contactInfo.lastName

View file

@ -52,7 +52,7 @@
cmsWidgetName="SiteFooterWidget" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
:isStackedVertically="true" :isStackedVertically="true"
@backClicked="navigateBack" @backClicked="backButtonAction"
@ForwardClicked="forwardButtonAction" /> @ForwardClicked="forwardButtonAction" />
</div> </div>
</div> </div>
@ -230,7 +230,7 @@ export default {
const customerReqs = !!(firstName && lastName && emailAddress); const customerReqs = !!(firstName && lastName && emailAddress);
// Contact Info // Contact Info
const { contactInfo } = useMainStore().order; const { contactInfo } = useMainStore();
const contactInfoReqs = !!( const contactInfoReqs = !!(
contactInfo.firstName contactInfo.firstName
&& contactInfo.lastName && contactInfo.lastName
@ -291,6 +291,15 @@ export default {
{ [routerParams.SKIP_SAVE_SESSION]: true } { [routerParams.SKIP_SAVE_SESSION]: true }
); );
} }
},
backButtonAction() {
const scenario = this.mainStore.isMobileAppointment
? this.navigationScenarios.CLICKED_BACK_MOBILE
: this.navigationScenarios.CLICKED_BACK_INSHOP;
this.$router.navigate(
scenario,
this.$route
);
} }
} }
}; };

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

@ -182,10 +182,12 @@ const defaultOrder = {
}, },
techNotes: '' techNotes: ''
}, },
contactInfo: { customer: {
firstName: 'first', firstName: 'first',
lastName: 'last', lastName: 'last',
emailAddress: 'builddigitaltest@safelite.com', emailAddress: 'builddigitaltest@safelite.com'
},
contactInfo: {
servicePhone: '555-555-5555' servicePhone: '555-555-5555'
}, },
damage: { damage: {

View file

@ -479,15 +479,15 @@ export default {
authSignature: '', authSignature: '',
authSignatureStart: '', authSignatureStart: '',
referralSequenceNumber: useMainStore().order.referralSequenceNumber, referralSequenceNumber: useMainStore().order.referralSequenceNumber,
emailAddress: useMainStore().order.contactInfo.emailAddress, emailAddress: useMainStore().contactInfo.emailAddress,
address1: this.getAddress1(), address1: this.getAddress1(),
address2: this.getAddress2(), address2: this.getAddress2(),
city: this.getCity(), city: this.getCity(),
state: this.getState(), state: this.getState(),
zipCode: this.getZipCode(), zipCode: this.getZipCode(),
firstName: useMainStore().order.contactInfo.firstName, firstName: useMainStore().contactInfo.firstName,
lastName: useMainStore().order.contactInfo.lastName, lastName: useMainStore().contactInfo.lastName,
phoneNumber: useMainStore().order.contactInfo.servicePhone, phoneNumber: useMainStore().contactInfo.servicePhone,
ctu: useMainStore().order.serviceLocation.provider?.address?.zipCodeCtu ?? useMainStore().order.serviceLocation.zipCodeCtu, ctu: useMainStore().order.serviceLocation.provider?.address?.zipCodeCtu ?? useMainStore().order.serviceLocation.zipCodeCtu,
referralCorrelationId: useMainStore().order.referralCorrelationId, referralCorrelationId: useMainStore().order.referralCorrelationId,
workOrderNumber: this.getWorkOrderNumber(), workOrderNumber: this.getWorkOrderNumber(),
@ -593,7 +593,7 @@ export default {
); );
// Contact Info // Contact Info
const { contactInfo } = useMainStore().order; const { contactInfo } = useMainStore();
const contactInfoReqs = !!( const contactInfoReqs = !!(
contactInfo.firstName contactInfo.firstName
&& contactInfo.lastName && contactInfo.lastName

View file

@ -372,9 +372,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,
@ -417,6 +414,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);
@ -454,6 +452,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) {
@ -523,6 +522,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);
@ -601,12 +601,6 @@ $page-side-padding: 1.5rem;
div.alert { div.alert {
margin-top: 0.625rem; margin-top: 0.625rem;
:deep(.alert-header) {
button.btn-collapse {
display: block;
}
}
button.looka-likea-link { button.looka-likea-link {
background: none; background: none;
border: none; border: none;

View file

@ -195,8 +195,12 @@ export default {
} }
store.updateVaps(this.selectedVaps); store.updateVaps(this.selectedVaps);
const scenario = store.isMobileAppointment
? this.navigationScenarios.CLICKED_FORWARD_MOBILE
: this.navigationScenarios.CLICKED_FORWARD_INSHOP;
this.$router.navigate( this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD, scenario,
this.$route this.$route
); );
} }

View file

@ -38,10 +38,12 @@ describe('contact-details-drawer', () => {
const servicePhone = '606-009-2943'; const servicePhone = '606-009-2943';
const mainInitialState = { const mainInitialState = {
order: { order: {
contactInfo: { customer: {
firstName, firstName,
lastName, lastName,
emailAddress, emailAddress
},
contactInfo: {
servicePhone servicePhone
} }
} }
@ -112,10 +114,12 @@ describe('contact-details-drawer', () => {
const phoneNumber = '123-456-1234'; const phoneNumber = '123-456-1234';
const mainInitialState = { const mainInitialState = {
order: { order: {
contactInfo: { customer: {
firstName, firstName,
lastName, lastName,
emailAddress, emailAddress
},
contactInfo: {
phoneNumber phoneNumber
} }
} }

View file

@ -388,10 +388,12 @@ describe('tpa-submit', () => {
companyName: providerCompanyName companyName: providerCompanyName
} }
}, },
contactInfo: { customer: {
firstName, firstName,
lastName, lastName,
emailAddress, emailAddress
},
contactInfo: {
servicePhone servicePhone
} }
} }
@ -433,10 +435,12 @@ describe('tpa-submit', () => {
companyName: providerCompanyName companyName: providerCompanyName
} }
}, },
contactInfo: { customer: {
firstName, firstName,
lastName, lastName,
emailAddress, emailAddress
},
contactInfo: {
servicePhone, servicePhone,
extension extension
} }

View file

@ -90,6 +90,10 @@ const navigationScenarios = Object.freeze({
CLICKED_FORWARD_WITH_TPA_DISABLED: 'CLICKED_FORWARD_WITH_TPA_DISABLED', CLICKED_FORWARD_WITH_TPA_DISABLED: 'CLICKED_FORWARD_WITH_TPA_DISABLED',
CLICKED_FORWARD_WITH_POLICY_AND_VEHICLES: 'CLICKED_FORWARD_WITH_POLICY_AND_VEHICLES', CLICKED_FORWARD_WITH_POLICY_AND_VEHICLES: 'CLICKED_FORWARD_WITH_POLICY_AND_VEHICLES',
// Service Package
CLICKED_FORWARD_INSHOP: 'CLICKED_FORWARD_INSHOP',
CLICKED_FORWARD_MOBILE: 'CLICKED_FORWARD_MOBILE',
// Review Page // Review Page
CLICKED_CUSTOMER_EDIT: 'CLICKED_CUSTOMER_EDIT', CLICKED_CUSTOMER_EDIT: 'CLICKED_CUSTOMER_EDIT',
CLICKED_DAMAGE_EDIT: 'CLICKED_DAMAGE_EDIT', CLICKED_DAMAGE_EDIT: 'CLICKED_DAMAGE_EDIT',
@ -99,6 +103,8 @@ const navigationScenarios = Object.freeze({
CLICKED_VEHICLE_EDIT: 'CLICKED_VEHICLE_EDIT', CLICKED_VEHICLE_EDIT: 'CLICKED_VEHICLE_EDIT',
// Payment // Payment
CLICKED_BACK_INSHOP: 'CLICKED_BACK_INSHOP',
CLICKED_BACK_MOBILE: 'CLICKED_BACK_MOBILE',
CLICKED_PAY_NOW: 'CLICKED_PAY_NOW', CLICKED_PAY_NOW: 'CLICKED_PAY_NOW',
PAY_IN_ADVANCE_ERROR: 'PAY_IN_ADVANCE_ERROR', PAY_IN_ADVANCE_ERROR: 'PAY_IN_ADVANCE_ERROR',
PAY_IN_ADVANCE_CREDIT_CARD_ERROR: 'PAY_IN_ADVANCE_CREDIT_CARD_ERROR', PAY_IN_ADVANCE_CREDIT_CARD_ERROR: 'PAY_IN_ADVANCE_CREDIT_CARD_ERROR',

View file

@ -608,7 +608,7 @@ const routingTable = () => [
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.CONTACT_DETAILS destinationIssPageValue: issPageValues.SERVICE_PACKAGES
} }
] ]
}, },
@ -620,12 +620,8 @@ const routingTable = () => [
destinationIssPageValue: issPageValues.SCHEDULE_PAGE destinationIssPageValue: issPageValues.SCHEDULE_PAGE
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP, scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.SERVICE_PACKAGES destinationIssPageValue: issPageValues.PAYMENT_METHOD
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP,
destinationIssPageValue: issPageValues.TPA_SUBMIT
} }
] ]
}, },
@ -634,11 +630,15 @@ const routingTable = () => [
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.CONTACT_DETAILS destinationIssPageValue: issPageValues.SCHEDULE_PAGE
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD_INSHOP,
destinationIssPageValue: issPageValues.PAYMENT_METHOD destinationIssPageValue: issPageValues.PAYMENT_METHOD
},
{
scenario: navigationScenarios.CLICKED_FORWARD_MOBILE,
destinationIssPageValue: issPageValues.CONTACT_DETAILS
} }
] ]
}, },
@ -646,9 +646,13 @@ const routingTable = () => [
issPageValue: issPageValues.PAYMENT_METHOD, issPageValue: issPageValues.PAYMENT_METHOD,
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK_INSHOP,
destinationIssPageValue: issPageValues.SERVICE_PACKAGES destinationIssPageValue: issPageValues.SERVICE_PACKAGES
}, },
{
scenario: navigationScenarios.CLICKED_BACK_MOBILE,
destinationIssPageValue: issPageValues.CONTACT_DETAILS
},
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.ORDER_CONFIRMATION destinationIssPageValue: issPageValues.ORDER_CONFIRMATION

View file

@ -191,9 +191,6 @@ export const getDefaultState = () => ({
} }
}, },
contactInfo: { contactInfo: {
firstName: null,
lastName: null,
emailAddress: null,
homePhone: null, homePhone: null,
alternativePhone: null, alternativePhone: null,
servicePhone: null, servicePhone: null,
@ -358,9 +355,9 @@ export const useMainStore = defineStore({
}; };
}, },
contactInfo: (s) => ({ contactInfo: (s) => ({
firstName: s.order.contactInfo.firstName ?? s.order.customer.firstName, firstName: s.order.customer.firstName,
lastName: s.order.contactInfo.lastName ?? s.order.customer.lastName, lastName: s.order.customer.lastName,
emailAddress: s.order.contactInfo.emailAddress ?? s.order.customer.emailAddress, 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,
@ -1472,9 +1469,9 @@ export const useMainStore = defineStore({
state: customer.address?.state, state: customer.address?.state,
zipCode: customer.address?.zipCode?.toString() zipCode: customer.address?.zipCode?.toString()
}, },
emailAddress: contactInfo.emailAddress || customer.emailAddress, emailAddress: customer.emailAddress,
firstName: contactInfo.firstName || customer.firstName, firstName: customer.firstName,
lastName: contactInfo.lastName || customer.lastName, lastName: customer.lastName,
homePhone: contactInfo.extension && contactInfo.homePhone ? contactInfo.homePhone + contactInfo.extension : contactInfo.homePhone, homePhone: contactInfo.extension && contactInfo.homePhone ? contactInfo.homePhone + contactInfo.extension : contactInfo.homePhone,
servicePhone: contactInfo.servicePhone, servicePhone: contactInfo.servicePhone,
alternativePhone: contactInfo.alternativePhone, alternativePhone: contactInfo.alternativePhone,
@ -1605,10 +1602,10 @@ export const useMainStore = defineStore({
order.customer.address.city = data.customer?.address?.city; order.customer.address.city = data.customer?.address?.city;
order.customer.address.state = data.customer?.address?.state; order.customer.address.state = data.customer?.address?.state;
order.customer.address.zipCode = data.customer?.address?.zipCode; order.customer.address.zipCode = data.customer?.address?.zipCode;
order.customer.firstName = data?.customer?.firstName;
order.customer.lastName = data?.customer?.lastName;
order.customer.emailAddress = data?.customer?.emailAddress;
order.contactInfo.firstName = data?.customer?.firstName;
order.contactInfo.lastName = data?.customer?.lastName;
order.contactInfo.emailAddress = data?.customer?.emailAddress;
order.contactInfo.extension = data?.customer?.homePhone?.slice(10); order.contactInfo.extension = data?.customer?.homePhone?.slice(10);
order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10); order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10);
order.contactInfo.servicePhone = data?.customer?.servicePhone; order.contactInfo.servicePhone = data?.customer?.servicePhone;
@ -1673,11 +1670,10 @@ export const useMainStore = defineStore({
order.customer.address.city = data.customer?.address?.city; order.customer.address.city = data.customer?.address?.city;
order.customer.address.state = data.customer?.address?.state; order.customer.address.state = data.customer?.address?.state;
order.customer.address.zipCode = data.customer?.address?.zipCode; order.customer.address.zipCode = data.customer?.address?.zipCode;
order.customer.emailAddress = data.customer?.emailAddress; order.customer.firstName = data?.customer?.firstName;
order.customer.lastName = data?.customer?.lastName;
order.customer.emailAddress = data?.customer?.emailAddress;
order.contactInfo.firstName = data?.customer?.firstName;
order.contactInfo.lastName = data?.customer?.lastName;
order.contactInfo.emailAddress = data?.customer?.emailAddress;
order.contactInfo.extension = data?.customer?.homePhone?.slice(10); order.contactInfo.extension = data?.customer?.homePhone?.slice(10);
order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10); order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10);
order.contactInfo.servicePhone = data?.customer?.servicePhone; order.contactInfo.servicePhone = data?.customer?.servicePhone;
@ -1823,27 +1819,29 @@ 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 = { if (serviceLocationInfo.provider) {
providerNumber: serviceLocationInfo.provider?.providerNumber, this.order.serviceLocation.provider = {
address: { providerNumber: serviceLocationInfo.provider?.providerNumber,
streetAddress: serviceLocationInfo.provider?.address?.streetAddress, address: {
city: serviceLocationInfo.provider?.address?.city, streetAddress: serviceLocationInfo.provider?.address?.streetAddress,
state: serviceLocationInfo.provider?.address?.state, city: serviceLocationInfo.provider?.address?.city,
zipCode: serviceLocationInfo.provider?.address?.zipCode, state: serviceLocationInfo.provider?.address?.state,
zipCodeCtu: serviceLocationInfo.provider?.address?.zipCodeCtu zipCode: serviceLocationInfo.provider?.address?.zipCode,
}, zipCodeCtu: serviceLocationInfo.provider?.address?.zipCodeCtu
companyName: serviceLocationInfo.provider?.companyName, },
phoneNumber: serviceLocationInfo.provider?.phoneNumber companyName: serviceLocationInfo.provider?.companyName,
}; phoneNumber: serviceLocationInfo.provider?.phoneNumber
};
}
this.order.serviceLocation.searchFilter = serviceLocationInfo.searchFilter; this.order.serviceLocation.searchFilter = serviceLocationInfo.searchFilter;
}, },
@ -2116,9 +2114,6 @@ export const useMainStore = defineStore({
}, },
resetContactInfo() { resetContactInfo() {
this.order.contactInfo.firstName = null;
this.order.contactInfo.lastName = null;
this.order.contactInfo.emailAddress = null;
this.order.contactInfo.homePhone = null; this.order.contactInfo.homePhone = null;
this.order.contactInfo.alternativePhone = null; this.order.contactInfo.alternativePhone = null;
this.order.contactInfo.servicePhone = null; this.order.contactInfo.servicePhone = null;
@ -2602,9 +2597,10 @@ export const useMainStore = defineStore({
}, },
updateContactInfo(contactInfo) { updateContactInfo(contactInfo) {
this.order.contactInfo.firstName = contactInfo?.firstName ?? ''; this.order.customer.firstName = contactInfo?.firstName ?? this.order.customer.firstName;
this.order.contactInfo.lastName = contactInfo?.lastName ?? ''; this.order.customer.lastName = contactInfo?.lastName ?? this.order.customer.lastName;
this.order.contactInfo.emailAddress = contactInfo?.emailAddress ?? ''; this.order.customer.emailAddress = contactInfo?.emailAddress ?? this.order.customer.emailAddress;
this.order.contactInfo.requestTextUpdates = contactInfo?.requestTextUpdates ?? false; this.order.contactInfo.requestTextUpdates = contactInfo?.requestTextUpdates ?? false;
this.order.contactInfo.notesForTechnician = contactInfo?.notesForTechnician ?? ''; this.order.contactInfo.notesForTechnician = contactInfo?.notesForTechnician ?? '';
}, },

View file

@ -486,7 +486,7 @@ describe('Store', () => {
// Arrange // Arrange
const firstName = getRandomString(4, 10); const firstName = getRandomString(4, 10);
const lastName = getRandomString(5, 15); const lastName = getRandomString(5, 15);
const emailAddress = false; const emailAddress = getRandomString(5, 15);
const requestTextUpdates = getRandomBoolean(); const requestTextUpdates = getRandomBoolean();
const notesForTechnician = getRandomString(50, 150); const notesForTechnician = getRandomString(50, 150);
@ -525,14 +525,14 @@ describe('Store', () => {
expect(store.contactInfo.alternativePhone).toEqual(altPhone); expect(store.contactInfo.alternativePhone).toEqual(altPhone);
expect(store.contactInfo.extension).toEqual(extension); expect(store.contactInfo.extension).toEqual(extension);
}); });
it('All null values => contact info set in store to all nulls', () => { it('All null values => contact info set in store to all null/default', () => {
// Act // Act
store.updateContactInfo({}); store.updateContactInfo({});
// Assert // Assert
expect(store.contactInfo.firstName).toEqual(''); expect(store.contactInfo.firstName).toEqual(null);
expect(store.contactInfo.lastName).toEqual(''); expect(store.contactInfo.lastName).toEqual(null);
expect(store.contactInfo.emailAddress).toEqual(''); expect(store.contactInfo.emailAddress).toEqual(null);
expect(store.contactInfo.requestTextUpdates).toEqual(false); expect(store.contactInfo.requestTextUpdates).toEqual(false);
expect(store.contactInfo.notesForTechnician).toEqual(''); expect(store.contactInfo.notesForTechnician).toEqual('');
}); });
@ -754,13 +754,13 @@ describe('Store', () => {
const contactServicePhone = getRandomString(6, 6); const contactServicePhone = getRandomString(6, 6);
const contactAlternativePhone = getRandomString(6, 6); const contactAlternativePhone = getRandomString(6, 6);
const requestTextUpdates = getRandomBoolean(); const requestTextUpdates = getRandomBoolean();
store.order.contactInfo.firstName = contactFirstName;
store.order.contactInfo.lastName = contactLastName;
store.order.contactInfo.emailAddress = contactEmail;
store.order.contactInfo.homePhone = contactHomePhone; store.order.contactInfo.homePhone = contactHomePhone;
store.order.contactInfo.servicePhone = contactServicePhone; store.order.contactInfo.servicePhone = contactServicePhone;
store.order.contactInfo.alternativePhone = contactAlternativePhone; store.order.contactInfo.alternativePhone = contactAlternativePhone;
store.order.contactInfo.requestTextUpdates = requestTextUpdates; store.order.contactInfo.requestTextUpdates = requestTextUpdates;
store.order.customer.firstName = contactFirstName;
store.order.customer.lastName = contactLastName;
store.order.customer.emailAddress = contactEmail;
store.order.customer.address.streetAddress = streetAddress; store.order.customer.address.streetAddress = streetAddress;
store.order.customer.address.streetAddress2 = streetAddress2; store.order.customer.address.streetAddress2 = streetAddress2;
store.order.customer.address.city = city; store.order.customer.address.city = city;

View file

@ -31,41 +31,44 @@
<slot name="headline-line-two"></slot> <slot name="headline-line-two"></slot>
</div> </div>
</template> </template>
<button <div class="alert-icon-container">
v-if="isCollapsible" <button
class="btn-collapse" v-if="isCollapsible"
type="button" class="btn-collapse"
@click="toggleCollapse"> type="button"
<svg @click="toggleCollapse">
xmlns="http://www.w3.org/2000/svg" <svg
width="15" xmlns="http://www.w3.org/2000/svg"
height="9" width="15"
viewBox="0 0 15 9" height="9"
class="btn-collapse-icon" viewBox="0 0 15 9"
:class="{ 'rotated': !collapsed }"> class="btn-collapse-icon"
<path :class="{ 'rotated': !collapsed }">
fill-rule="nonzero" <path
d="M7.5 0a.806.806 0 0 0-.593.265L.246 7.455a.957.957 0 0 0 0 1.28.796.796 0 0 0 fill-rule="nonzero"
1.185 0l6.07-6.55 6.068 6.55a.796.796 0 0 0 1.186 0 .957.957 0 0 0 0-1.28L8.093.265A.806.806 d="M7.5 0a.806.806 0 0 0-.593.265L.246 7.455a.957.957 0 0 0 0 1.28.796.796 0 0 0
0 0 0 7.5 0" /> 1.185 0l6.07-6.55 6.068 6.55a.796.796 0 0 0 1.186 0 .957.957 0 0 0 0-1.28L8.093.265A.806.806
</svg> 0 0 0 7.5 0" />
</button> </svg>
<button </button>
v-if="isDismissible" <button
type="button" v-if="isDismissible"
class="btn-close p-2" type="button"
data-bs-dismiss="alert" class="btn-dismiss"
aria-label="Close"> data-bs-dismiss="alert"
<svg aria-label="Close">
xmlns="http://www.w3.org/2000/svg" <svg
viewBox="0 0 23.7 23.7" xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"> viewBox="0 0 23.7 23.7"
<path class="btn-dismiss-icon"
d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581 xml:space="preserve">
1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21 <path
.46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z" /> d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581
</svg> 1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21
</button> .46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z" />
</svg>
</button>
</div>
</div> </div>
<div <div
v-show="alertCopy" v-show="alertCopy"
@ -297,18 +300,22 @@ export default {
transform: none; transform: none;
} }
} }
.btn-dismiss {
display: flex;
border: none;
background: none;
min-width: 1rem;
max-height: 1rem;
padding: 0;
}
.btn-dismiss-icon {
height: 100%;
width: 100%;
}
a { a {
font-weight: $font-weight-normal; font-weight: $font-weight-normal;
} }
border-color: transparent; border-color: transparent;
.btn-close {
background: none;
opacity: 1;
width: 0.75rem;
height: 0.75rem;
top: 2px;
right: 2px;
}
.alert-body { .alert-body {
margin: 0 1rem; margin: 0 1rem;
padding: .5rem 1rem .75rem 1rem; padding: .5rem 1rem .75rem 1rem;