Merge pull request #3303 from Safelite/feature/CASH-1524

Feature/CASH 1524
This commit is contained in:
Chris 2026-08-03 13:56:22 -04:00 committed by GitHub
commit f42aca6636
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 602 additions and 70 deletions

View file

@ -0,0 +1,10 @@
const saveProgressCmsWidgets = {
PHONE_SPECIFIC: "SaveProgressPopupWidget_PhoneSpecific",
EMAIL_SPECIFIC: "SaveProgressPopupWidget_EmailSpecific",
MODAL_PHONE_QUESTION: "SaveProgressPhoneQuestionWidget",
MODAL_EMAIL_QUESTION: "SaveProgressEmailQuestionWidget",
POPUP_PHONE_QUESTION: "SaveProgressPopupPhoneQuestionWidget",
POPUP_EMAIL_QUESTION: "SaveProgressPopupEmailQuestionWidget",
};
export { saveProgressCmsWidgets };

View file

@ -1,43 +1,261 @@
import { mount, shallowMount } from "@vue/test-utils"; import { mount } from "@vue/test-utils";
import saveProgressModalQuestion from "./save-progress-modal-question"; import saveProgressModalQuestion from "./save-progress-modal-question";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { storeActions } from "@/constants/store-actions";
import { saveProgressCmsWidgets } from "@/constants/save-progress-cms-widgets";
import { saveQuote } from "@/helpers/heritage-integration/order-helper.js";
import experimentMixin from "@/mixins/experiment-mixin.js";
import { experimentSettings } from "@/constants/experiments";
jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({
saveQuote: jest.fn().mockResolvedValue(undefined),
}));
jest.mock("@/helpers/save-progress-sms-consent/save-progress-sms-consent-helper", () => ({
...jest.requireActual("@/helpers/save-progress-sms-consent/save-progress-sms-consent-helper"),
getSaveProgressSmsConsentConfig: jest.fn().mockReturnValue({
copy: {
transactional: "Sign me up for updates about my upcoming service.",
marketing: "Sign me up for promotional and product offers.",
},
value: { transactional: false, marketing: false },
}),
}));
jest.mock("@/digital-components/modal/modal", () => ({ jest.mock("@/digital-components/modal/modal", () => ({
methods: { methods: {
openModal: jest.fn(), openModal: jest.fn(),
closeModal: jest.fn(),
resetButtonStyle: jest.fn(), resetButtonStyle: jest.fn(),
resetForm: jest.fn(),
validate: jest.fn().mockResolvedValue({ valid: true }),
}, },
})); }));
describe("save-progress-modal-question ", () => { jest.mock("@/ux-components/alert/alert", () => ({
describe("when openModal is run ", () => { name: "alert",
test("the modal should open ", () => { template: "<div />",
// Arrange }));
describe("save-progress-modal-question", () => {
beforeEach(() => {
jest.clearAllMocks();
experimentMixin.methods.hasSettingEqualTo = jest.fn().mockReturnValue(false);
});
describe("when openModal is run", () => {
test("the modal should open", () => {
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
props: { props: {
modelValue: "",
modalWidgetName: "testModal", modalWidgetName: "testModal",
}, },
}); });
// Act
wrapper.vm.openModal(); wrapper.vm.openModal();
// Assert
expect(wrapper.vm.modal).not.toBeNull(); expect(wrapper.vm.modal).not.toBeNull();
}); });
}); });
describe("legacy email-only mode", () => {
test("should save email to the store", async () => {
const { wrapper, dispatchStoreAction } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
pageName: "part-questions",
},
});
wrapper.vm.userInput = "test@example.com";
await wrapper.vm.saveProgress();
expect(dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_EMAIL,
"test@example.com",
false
);
expect(saveQuote).toHaveBeenCalledWith({ pageNameToLog: "part-questions" });
expect(wrapper.vm.isProgressSaved).toBe(true);
});
test("should use the legacy disclaimer widget", () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
},
cmsContent: {
SaveProgressModalWidget: { FooterText2: "Legacy disclaimer" },
},
});
expect(wrapper.vm.modalDisclaimerText).toBe("Legacy disclaimer");
});
});
describe("consent management mode", () => {
beforeEach(() => {
experimentMixin.methods.hasSettingEqualTo = jest
.fn()
.mockImplementation(
(settingName, settingValue) =>
settingName === experimentSettings.SHOW_CONSENT_MANAGEMENT &&
settingValue === "true"
);
});
test("should default to the phone tab when opened", () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
},
});
wrapper.vm.openModal();
expect(wrapper.vm.isPhoneTabSelected).toBe(true);
});
test("should use popup-specific tab labels from CMS widgets", () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
},
cmsContent: {
SaveProgressPopupWidget_PhoneSpecific: { HeadlineText: "PHONE" },
SaveProgressPopupWidget_EmailSpecific: { HeadlineText: "EMAIL" },
},
});
expect(wrapper.vm.contactTabs).toEqual([
{ label: "PHONE", value: "PhoneAnswer" },
{ label: "EMAIL", value: "EmailAnswer" },
]);
});
test("should use the modal question CMS widgets for phone and email inputs", () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
},
});
expect(wrapper.vm.phoneQuestionWidgetName).toBe(
saveProgressCmsWidgets.MODAL_PHONE_QUESTION
);
expect(wrapper.vm.emailQuestionWidgetName).toBe(
saveProgressCmsWidgets.MODAL_EMAIL_QUESTION
);
});
test("should use the email-specific popup disclaimer on the email tab", async () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
},
cmsContent: {
SaveProgressPopupWidget_EmailSpecific: { BodyText: "Email disclaimer" },
SaveProgressPopupWidget_PhoneSpecific: { BodyText: "Phone disclaimer" },
},
});
wrapper.vm.selectContactMethod("EmailAnswer");
await wrapper.vm.$nextTick();
expect(wrapper.vm.modalDisclaimerText).toBe("Email disclaimer");
});
test("should use the phone-specific popup disclaimer on the phone tab", () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
},
cmsContent: {
SaveProgressPopupWidget_EmailSpecific: { BodyText: "Email disclaimer" },
SaveProgressPopupWidget_PhoneSpecific: { BodyText: "Phone disclaimer" },
},
});
wrapper.vm.contactMethod = "PhoneAnswer";
expect(wrapper.vm.modalDisclaimerText).toBe("Phone disclaimer");
});
test("should show consent errors when phone is valid but transactional consent is missing", async () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
},
});
wrapper.vm.contactMethod = "PhoneAnswer";
wrapper.vm.userInput = "5551234567";
wrapper.vm.smsConsent = { transactional: false, marketing: false };
await wrapper.vm.validatePhoneAndSave();
expect(wrapper.vm.showConsentErrors).toBe(true);
});
test("should save phone and sms consent when valid", async () => {
const { wrapper, dispatchStoreAction } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
pageName: "vehicle-parts",
},
});
wrapper.vm.contactMethod = "PhoneAnswer";
wrapper.vm.userInput = "555-123-4567";
wrapper.vm.smsConsent = { transactional: true, marketing: true };
await wrapper.vm.saveProgress();
expect(dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_PHONE_NUMBER,
"5551234567",
false
);
expect(dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_IS_SMS_OPT_IN,
true,
false
);
expect(dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_IS_SMS_MARKETING_OPT_IN,
true,
false
);
expect(saveQuote).toHaveBeenCalledWith({ pageNameToLog: "vehicle-parts" });
});
test("should reset sms consent when switching tabs", async () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressModalWidget",
},
});
wrapper.vm.contactMethod = "PhoneAnswer";
wrapper.vm.smsConsent = { transactional: true, marketing: true };
wrapper.vm.selectContactMethod("EmailAnswer");
await wrapper.vm.$nextTick();
expect(wrapper.vm.smsConsent).toEqual({ transactional: false, marketing: false });
});
});
}); });
function setupMocks({ options, props }) { function setupMocks({ options, props, cmsContent = {} }) {
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
...options, ...options,
}); });
const dispatchStoreAction = jest.fn().mockResolvedValue(undefined);
const mockBaseMixin = { const mockBaseMixin = {
methods: { methods: {
getCmsContent: jest.fn(), getCmsContent: jest.fn((widgetName, fieldName) => cmsContent[widgetName]?.[fieldName]),
dispatchStoreAction: jest.fn(), dispatchStoreAction,
}, },
}; };
@ -47,5 +265,5 @@ function setupMocks({ options, props }) {
const wrapper = mount(saveProgressModalQuestion, mountOptions); const wrapper = mount(saveProgressModalQuestion, mountOptions);
return { wrapper }; return { wrapper, dispatchStoreAction };
} }

View file

@ -17,13 +17,66 @@
:onModalClosedCallback="onModalClosed" :onModalClosedCallback="onModalClosed"
:footerButtonText="modalButtonText" :footerButtonText="modalButtonText"
:isFooterButtonPrimary="true" :isFooterButtonPrimary="true"
:isFooterButtonSuppressed="isPhoneTabSelected && !isProgressSaved"
@footer-button-event="saveProgress"> @footer-button-event="saveProgress">
<p class="modal-body-inner" v-html="modalBodyText"></p> <p class="modal-body-inner" v-html="modalBodyText"></p>
<template v-if="showConsentManagement">
<fieldset class="save-progress-modal-question__tabs">
<div class="save-progress-modal-question__tab-list" role="radiogroup">
<label
v-for="tab in contactTabs"
:key="tab.value"
class="save-progress-modal-question__tab"
:class="{
'save-progress-modal-question__tab--active':
contactMethod === tab.value,
'save-progress-modal-question__tab--disabled':
isContactMethodTabDisabled(tab.value),
}">
<input
type="radio"
class="save-progress-modal-question__tab-input"
name="saveProgressModalContactMethod"
:value="tab.value"
:checked="contactMethod === tab.value"
:disabled="isContactMethodTabDisabled(tab.value)"
@change="selectContactMethod(tab.value)" />
<span class="save-progress-modal-question__tab-label">
{{ tab.label }}
</span>
</label>
</div>
</fieldset>
<div v-if="isPhoneTabSelected" class="save-progress-modal-question__phone-fields">
<phoneNumberQuestion
v-model="userInput"
:cmsWidgetName="phoneQuestionWidgetName"
placeholderText=""
:isDisabled="isProgressSaved"
isRequired
validationRules="save-progress-modal-phone-number-required" />
<saveProgressPopupSmsConsentQuestion
v-model="smsConsent"
:consentCopy="smsConsentCopy"
:isDisabled="isProgressSaved"
:showConsentErrors="showConsentErrors"
:showConsentManagement="showConsentManagement" />
</div>
</template>
<saveProgressQuestion <saveProgressQuestion
v-if="!showConsentManagement || !isPhoneTabSelected"
ref="saveProgressQuestion" ref="saveProgressQuestion"
v-model="userInput" v-model="userInput"
cmsWidgetName="SaveProgressQuestionWidget" /> :cmsWidgetName="emailQuestionWidgetName"
:isDisabled="isProgressSaved" />
<template v-slot:modal-footer-slot> <template v-slot:modal-footer-slot>
<modalButtonMain
v-if="showConsentManagement && isPhoneTabSelected && !isProgressSaved"
ref="phoneSendButton"
:isPrimary="true"
class="w-100 modal-footer-button"
:buttonText="modalButtonText"
@click-event="validatePhoneAndSave" />
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p> <p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
</template> </template>
</modal> </modal>
@ -37,11 +90,34 @@
<script> <script>
import saveProgressQuestion from "@/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question"; import saveProgressQuestion from "@/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question";
import saveProgressPopupSmsConsentQuestion from "@/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question";
import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question";
import modal from "@/digital-components/modal/modal"; import modal from "@/digital-components/modal/modal";
import { storeActions } from "@/constants/store-actions.js"; import modalButtonMain from "@/digital-components/modal/ux-components/modal-button-main/modal-button-main";
import buttonMain from "@/ux-components/button-main/button-main"; import buttonMain from "@/ux-components/button-main/button-main";
import alert from "@/ux-components/alert/alert"; import alert from "@/ux-components/alert/alert";
import { saveQuote } from "@/helpers/heritage-integration/order-helper.js"; import { saveQuote } from "@/helpers/heritage-integration/order-helper.js";
import {
saveProgressPopupContactToStore,
saveProgressPopupContactMethods,
} from "@/helpers/save-progress-popup-contact-helper";
import {
defaultSaveProgressSmsConsent,
getSaveProgressSmsConsentFallbackCopy,
getSaveProgressSmsConsentConfig,
hasSaveProgressSmsConsentSelection,
} from "@/helpers/save-progress-sms-consent/save-progress-sms-consent-helper";
import experimentMixin from "@/mixins/experiment-mixin.js";
import { experimentSettings } from "@/constants/experiments";
import { saveProgressCmsWidgets } from "@/constants/save-progress-cms-widgets";
import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
const { PHONE_SPECIFIC, EMAIL_SPECIFIC, MODAL_PHONE_QUESTION, MODAL_EMAIL_QUESTION } =
saveProgressCmsWidgets;
defineRule("save-progress-modal-phone-number-required", required(errorMessages.PHONE_REQUIRED));
export default { export default {
name: "save-progress-modal-question", name: "save-progress-modal-question",
@ -49,15 +125,35 @@ export default {
data() { data() {
return { return {
userInput: "", userInput: "",
contactMethod: saveProgressPopupContactMethods.EMAIL,
smsConsent: defaultSaveProgressSmsConsent(),
smsConsentCopy: getSaveProgressSmsConsentFallbackCopy(),
isProgressSaved: false, isProgressSaved: false,
savedContactMethod: null,
showConsentErrors: false,
}; };
}, },
watch: {
smsConsent: {
handler(consent) {
if (hasSaveProgressSmsConsentSelection(consent)) {
this.showConsentErrors = false;
}
},
deep: true,
},
},
props: { props: {
modelValue: Object,
modalWidgetName: String, modalWidgetName: String,
pageName: String, pageName: String,
}, },
computed: { computed: {
showConsentManagement() {
return experimentMixin.methods.hasSettingEqualTo(
experimentSettings.SHOW_CONSENT_MANAGEMENT,
"true"
);
},
buttonText() { buttonText() {
return this.getCmsContent(this.modalWidgetName, "SubheaderText"); return this.getCmsContent(this.modalWidgetName, "SubheaderText");
}, },
@ -71,7 +167,13 @@ export default {
return this.getCmsContent(this.modalWidgetName, "FooterText"); return this.getCmsContent(this.modalWidgetName, "FooterText");
}, },
modalDisclaimerText() { modalDisclaimerText() {
return this.getCmsContent(this.modalWidgetName, "FooterText2"); if (!this.showConsentManagement) {
return this.getCmsContent(this.modalWidgetName, "FooterText2");
}
const disclaimerWidgetName = this.isPhoneTabSelected ? PHONE_SPECIFIC : EMAIL_SPECIFIC;
return this.getCmsContent(disclaimerWidgetName, "BodyText");
}, },
modalName() { modalName() {
return this.modalWidgetName; return this.modalWidgetName;
@ -79,34 +181,121 @@ export default {
modal() { modal() {
return this.$refs[this.modalName]; return this.$refs[this.modalName];
}, },
contactTabs() {
return [
{
label: this.getCmsContent(PHONE_SPECIFIC, "HeadlineText") || "Phone",
value: saveProgressPopupContactMethods.PHONE,
},
{
label: this.getCmsContent(EMAIL_SPECIFIC, "HeadlineText") || "Email",
value: saveProgressPopupContactMethods.EMAIL,
},
];
},
isPhoneTabSelected() {
return this.contactMethod === saveProgressPopupContactMethods.PHONE;
},
phoneQuestionWidgetName() {
return MODAL_PHONE_QUESTION;
},
emailQuestionWidgetName() {
return MODAL_EMAIL_QUESTION;
},
}, },
methods: { methods: {
loadSmsConsentConfig() {
const config = getSaveProgressSmsConsentConfig();
this.smsConsentCopy = config.copy;
if (!this.isProgressSaved) {
this.smsConsent = config.value;
}
},
resetContactFormState() {
this.userInput = "";
this.contactMethod = this.showConsentManagement
? saveProgressPopupContactMethods.PHONE
: saveProgressPopupContactMethods.EMAIL;
this.smsConsent = defaultSaveProgressSmsConsent();
this.showConsentErrors = false;
this.savedContactMethod = null;
},
selectContactMethod(contactMethod) {
if (this.isProgressSaved || this.contactMethod === contactMethod) {
return;
}
this.contactMethod = contactMethod;
this.userInput = "";
this.smsConsent = defaultSaveProgressSmsConsent();
this.showConsentErrors = false;
},
isContactMethodTabDisabled(contactMethod) {
return this.isProgressSaved && contactMethod !== this.savedContactMethod;
},
openModal() { openModal() {
this.resetContactFormState();
this.loadSmsConsentConfig();
this.modal.openModal(); this.modal.openModal();
this.modal.resetButtonStyle(); this.modal.resetButtonStyle();
}, },
onModalClosed() { onModalClosed() {
this.userInput = ""; this.resetContactFormState();
this.modal.resetForm(); this.modal.resetForm();
}, },
async saveProgress() { async validatePhoneAndSave() {
// save email address to store try {
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.userInput, false); const validationResult = await this.modal.validate();
if (!validationResult.valid) {
return;
}
if (!hasSaveProgressSmsConsentSelection(this.smsConsent)) {
this.showConsentErrors = true;
return;
}
this.showConsentErrors = false;
await this.saveProgress();
} finally {
this.resetPhoneSendButtonStyle();
}
},
resetPhoneSendButtonStyle() {
this.$refs.phoneSendButton?.resetButtonStyle();
},
async saveProgress() {
await saveProgressPopupContactToStore(this.dispatchStoreAction, {
contactMethod: this.showConsentManagement
? this.contactMethod
: saveProgressPopupContactMethods.EMAIL,
userInput: this.userInput,
smsConsent:
this.showConsentManagement && this.isPhoneTabSelected
? this.smsConsent
: defaultSaveProgressSmsConsent(),
});
// send store call to send to new API (that triggers an email send)
await saveQuote({ pageNameToLog: this.pageName }); await saveQuote({ pageNameToLog: this.pageName });
// hide save progress button; show success message alert if (this.showConsentManagement) {
this.isProgressSaved = true; this.savedContactMethod = this.contactMethod;
}
this.isProgressSaved = true;
this.modal.closeModal(); this.modal.closeModal();
}, },
}, },
components: { components: {
modal, modal,
modalButtonMain,
buttonMain, buttonMain,
saveProgressQuestion, saveProgressQuestion,
phoneNumberQuestion,
saveProgressPopupSmsConsentQuestion,
alert, alert,
}, },
}; };
@ -178,7 +367,6 @@ export default {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
&.delay { &.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out; transition: background 0s 0s ease-in-out;
} }
@ -218,6 +406,90 @@ export default {
text-align: center; text-align: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.save-progress-modal-question__tabs {
border: 0;
margin: 0 0 1rem 0;
padding: 0;
}
.save-progress-modal-question__tab-list {
display: flex;
width: 100%;
}
.save-progress-modal-question__tab {
flex: 1 1 50%;
display: grid;
min-height: 3.25rem;
margin: 0;
cursor: pointer;
}
.save-progress-modal-question__tab-input {
grid-area: 1 / 1;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: inherit;
&:focus-visible + .save-progress-modal-question__tab-label {
outline: 2px solid $blue;
outline-offset: 2px;
border-radius: 0.25rem;
}
}
.save-progress-modal-question__tab-label {
grid-area: 1 / 1;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 0.875rem 0.5rem;
text-align: center;
text-transform: uppercase;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.25rem;
color: $gray-600;
border-bottom: 3px solid transparent;
transition:
color 150ms linear,
border-color 150ms linear;
}
.save-progress-modal-question__tab--active .save-progress-modal-question__tab-label {
color: $red;
border-bottom-color: $red;
}
.save-progress-modal-question__tab--disabled {
cursor: not-allowed;
.save-progress-modal-question__tab-label {
color: $gray-400;
}
}
.save-progress-modal-question__phone-fields {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin: 0 0 1.5rem 0;
text-align: left;
}
.phone-number-question {
margin: 0;
label {
text-align: left;
font-weight: 900;
}
}
.modal-disclaimer { .modal-disclaimer {
font-size: 0.75rem; font-size: 0.75rem;
order: 2; order: 2;

View file

@ -111,11 +111,10 @@ import {
import { defineRule } from "vee-validate"; import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import { saveProgressCmsWidgets } from "@/constants/save-progress-cms-widgets";
const PHONE_SPECIFIC_WIDGET = "SaveProgressPopupWidget_PhoneSpecific"; const { PHONE_SPECIFIC, EMAIL_SPECIFIC, POPUP_PHONE_QUESTION, POPUP_EMAIL_QUESTION } =
const EMAIL_SPECIFIC_WIDGET = "SaveProgressPopupWidget_EmailSpecific"; saveProgressCmsWidgets;
const PHONE_QUESTION_WIDGET = "SaveProgressPopupPhoneQuestionWidget";
const EMAIL_QUESTION_WIDGET = "SaveProgressPopupEmailQuestionWidget";
defineRule("phone-number-required", required(errorMessages.PHONE_REQUIRED)); defineRule("phone-number-required", required(errorMessages.PHONE_REQUIRED));
@ -136,7 +135,7 @@ export default {
watch: { watch: {
smsConsent: { smsConsent: {
handler(consent) { handler(consent) {
if (hasSaveProgressSmsConsentSelection(consent, this.showConsentManagement)) { if (hasSaveProgressSmsConsentSelection(consent)) {
this.showConsentErrors = false; this.showConsentErrors = false;
} }
}, },
@ -171,9 +170,7 @@ export default {
return this.getCmsContent(this.modalWidgetName, "FooterText"); return this.getCmsContent(this.modalWidgetName, "FooterText");
}, },
modalDisclaimerText() { modalDisclaimerText() {
const disclaimerWidgetName = this.isPhoneTabSelected const disclaimerWidgetName = this.isPhoneTabSelected ? PHONE_SPECIFIC : EMAIL_SPECIFIC;
? PHONE_SPECIFIC_WIDGET
: EMAIL_SPECIFIC_WIDGET;
return this.getCmsContent(disclaimerWidgetName, "BodyText"); return this.getCmsContent(disclaimerWidgetName, "BodyText");
}, },
@ -186,11 +183,11 @@ export default {
contactTabs() { contactTabs() {
return [ return [
{ {
label: this.getCmsContent(PHONE_SPECIFIC_WIDGET, "HeadlineText") || "Phone", label: this.getCmsContent(PHONE_SPECIFIC, "HeadlineText") || "Phone",
value: saveProgressPopupContactMethods.PHONE, value: saveProgressPopupContactMethods.PHONE,
}, },
{ {
label: this.getCmsContent(EMAIL_SPECIFIC_WIDGET, "HeadlineText") || "Email", label: this.getCmsContent(EMAIL_SPECIFIC, "HeadlineText") || "Email",
value: saveProgressPopupContactMethods.EMAIL, value: saveProgressPopupContactMethods.EMAIL,
}, },
]; ];
@ -202,10 +199,10 @@ export default {
return this.isProgressSaved || this.isPhoneTabSelected; return this.isProgressSaved || this.isPhoneTabSelected;
}, },
phoneQuestionWidgetName() { phoneQuestionWidgetName() {
return PHONE_QUESTION_WIDGET; return POPUP_PHONE_QUESTION;
}, },
emailQuestionWidgetName() { emailQuestionWidgetName() {
return EMAIL_QUESTION_WIDGET; return POPUP_EMAIL_QUESTION;
}, },
}, },
methods: { methods: {
@ -245,9 +242,7 @@ export default {
return; return;
} }
if ( if (!hasSaveProgressSmsConsentSelection(this.smsConsent)) {
!hasSaveProgressSmsConsentSelection(this.smsConsent, this.showConsentManagement)
) {
this.showConsentErrors = true; this.showConsentErrors = true;
await this.scrollConsentIntoView(); await this.scrollConsentIntoView();
return; return;

View file

@ -68,10 +68,7 @@ export default {
}, },
}, },
showConsentError() { showConsentError() {
return ( return this.showConsentErrors && !hasSaveProgressSmsConsentSelection(this.modelValue);
this.showConsentErrors &&
!hasSaveProgressSmsConsentSelection(this.modelValue, this.showConsentManagement)
);
}, },
consentErrorMessage() { consentErrorMessage() {
return this.showConsentManagement return this.showConsentManagement

View file

@ -9,6 +9,17 @@ export function isPhoneContactMethod(contactMethod) {
return contactMethod === saveProgressPopupContactMethods.PHONE; return contactMethod === saveProgressPopupContactMethods.PHONE;
} }
export function hasSaveProgressContactInStore(customer) {
if (!customer) {
return false;
}
const hasEmail = Boolean(customer.emailAddress?.length > 0);
const hasPhone = Boolean(customer.phoneNumber?.length > 0);
return hasEmail || hasPhone;
}
export function normalizePhoneNumberForStore(phoneNumber) { export function normalizePhoneNumberForStore(phoneNumber) {
if (!phoneNumber) { if (!phoneNumber) {
return phoneNumber; return phoneNumber;

View file

@ -1,5 +1,6 @@
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { import {
hasSaveProgressContactInStore,
isPhoneContactMethod, isPhoneContactMethod,
normalizePhoneNumberForStore, normalizePhoneNumberForStore,
saveProgressPopupContactMethods, saveProgressPopupContactMethods,
@ -11,6 +12,27 @@ describe("save-progress-popup-contact-helper", () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
describe("hasSaveProgressContactInStore", () => {
it("should return true when email is saved", () => {
expect(hasSaveProgressContactInStore({ emailAddress: "test@example.com" })).toBe(true);
});
it("should return true when phone is saved", () => {
expect(hasSaveProgressContactInStore({ phoneNumber: "5551234567" })).toBe(true);
});
it("should return false when customer is undefined", () => {
expect(hasSaveProgressContactInStore(undefined)).toBe(false);
});
it("should return false when neither email nor phone is saved", () => {
expect(hasSaveProgressContactInStore({})).toBe(false);
expect(hasSaveProgressContactInStore({ emailAddress: "", phoneNumber: null })).toBe(
false
);
});
});
describe("isPhoneContactMethod", () => { describe("isPhoneContactMethod", () => {
it("should return true for the phone contact method", () => { it("should return true for the phone contact method", () => {
expect(isPhoneContactMethod(saveProgressPopupContactMethods.PHONE)).toBe(true); expect(isPhoneContactMethod(saveProgressPopupContactMethods.PHONE)).toBe(true);

View file

@ -16,8 +16,8 @@ export function getSaveProgressSmsConsentFallbackCopy() {
return { ...SAVE_PROGRESS_SMS_CONSENT_FALLBACK_COPY }; return { ...SAVE_PROGRESS_SMS_CONSENT_FALLBACK_COPY };
} }
export function hasSaveProgressSmsConsentSelection(consent, showConsentManagement = false) { export function hasSaveProgressSmsConsentSelection(consent) {
return Boolean(consent?.transactional || (showConsentManagement && consent?.marketing)); return Boolean(consent?.transactional);
} }
export function getSaveProgressSmsConsentConfig() { export function getSaveProgressSmsConsentConfig() {

View file

@ -48,19 +48,16 @@ describe("save-progress-sms-consent-helper", () => {
expect(hasSaveProgressSmsConsentSelection(defaultSaveProgressSmsConsent())).toBe(false); expect(hasSaveProgressSmsConsentSelection(defaultSaveProgressSmsConsent())).toBe(false);
}); });
it("should return true when either consent option is selected and consent management is enabled", () => { it("should return true only when transactional consent is selected", () => {
expect( expect(
hasSaveProgressSmsConsentSelection({ transactional: true, marketing: false }, true) hasSaveProgressSmsConsentSelection({ transactional: true, marketing: false })
).toBe(true); ).toBe(true);
expect( expect(
hasSaveProgressSmsConsentSelection({ transactional: false, marketing: true }, true) hasSaveProgressSmsConsentSelection({ transactional: false, marketing: true })
).toBe(true);
});
it("should ignore marketing consent when consent management is disabled", () => {
expect(
hasSaveProgressSmsConsentSelection({ transactional: false, marketing: true }, false)
).toBe(false); ).toBe(false);
expect(
hasSaveProgressSmsConsentSelection({ transactional: true, marketing: true })
).toBe(true);
}); });
}); });
}); });

View file

@ -26,6 +26,7 @@
// Components // Components
import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/questions-page-layout"; import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/questions-page-layout";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question"; import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
import { hasSaveProgressContactInStore } from "@/helpers/save-progress-popup-contact-helper";
// Supporting Files // Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -59,12 +60,12 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const emailFromStore = store.getters.order.customer.emailAddress;
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.showSaveProgressModal = !(emailFromStore?.length > 0); vm.showSaveProgressModal = !hasSaveProgressContactInStore(
store.getters.order?.customer
);
if (store.getters.externalParameterState?.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) { if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction( await baseMixin.methods.dispatchStoreAction(

View file

@ -57,6 +57,7 @@
<saveProgressModalQuestion <saveProgressModalQuestion
modalWidgetName="SaveProgressModalWidget" modalWidgetName="SaveProgressModalWidget"
modalName="SaveProgressModal" modalName="SaveProgressModal"
v-if="showSaveProgressModal"
pageName="coverage-statement" /> pageName="coverage-statement" />
<navbar <navbar
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
@ -80,7 +81,8 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import customerInstructions from "./customer-instructions/customer-instructions.vue"; import customerInstructions from "./customer-instructions/customer-instructions.vue";
import priceDisplay from "./price-display/price-display.vue"; import priceDisplay from "./price-display/price-display.vue";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question.vue"; import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
import { hasSaveProgressContactInStore } from "@/helpers/save-progress-popup-contact-helper";
import afterpayBreakout from "../payment-method/afterpay-breakout/afterpay-breakout.vue"; import afterpayBreakout from "../payment-method/afterpay-breakout/afterpay-breakout.vue";
import modal from "@/digital-components/modal/modal"; import modal from "@/digital-components/modal/modal";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal.vue"; import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal.vue";
@ -105,6 +107,7 @@ export default {
isRecalibrationOnOrder: this.isRecalibrationOnOrderFromStore(), isRecalibrationOnOrder: this.isRecalibrationOnOrderFromStore(),
isRepair: this.isRepairFromStore(), isRepair: this.isRepairFromStore(),
clientConfig: {}, clientConfig: {},
showSaveProgressModal: null,
}; };
}, },
components: { components: {
@ -140,9 +143,11 @@ export default {
} }
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const showSaveProgressModal = !hasSaveProgressContactInStore(store.getters.order?.customer);
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.clientConfig = resultMap.clientConfig ?? {}; vm.clientConfig = resultMap.clientConfig ?? {};
vm.showSaveProgressModal = showSaveProgressModal;
debugLog( debugLog(
`coverage-statement clientConfig::${clientConfigPageName}`, `coverage-statement clientConfig::${clientConfigPageName}`,
JSON.stringify(vm.clientConfig) JSON.stringify(vm.clientConfig)

View file

@ -151,6 +151,7 @@ import { stateOptionsInternational } from "@/constants/state-options";
import { regex } from "@/helpers/validation-rules"; import { regex } from "@/helpers/validation-rules";
import { coverageStatus, coverageStatusEnum } from "@/constants/insurance"; import { coverageStatus, coverageStatusEnum } from "@/constants/insurance";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question"; import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
import { hasSaveProgressContactInStore } from "@/helpers/save-progress-popup-contact-helper";
import { debugLog } from "@/helpers/debug-log-helper"; import { debugLog } from "@/helpers/debug-log-helper";
import { import {
flushPagePrereqsLogs, flushPagePrereqsLogs,
@ -223,8 +224,7 @@ export default {
}, },
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const emailFromStore = store.getters.order.customer.emailAddress; const showSaveProgressModal = !hasSaveProgressContactInStore(store.getters.order?.customer);
const showSaveProgressModal = !(emailFromStore?.length > 0);
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.showSaveProgressModal = showSaveProgressModal; vm.showSaveProgressModal = showSaveProgressModal;

View file

@ -26,6 +26,7 @@
// Components // Components
import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/questions-page-layout"; import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/questions-page-layout";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question"; import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
import { hasSaveProgressContactInStore } from "@/helpers/save-progress-popup-contact-helper";
// Supporting Files // Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -59,12 +60,12 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const emailFromStore = store.getters.order.customer.emailAddress;
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.showSaveProgressModal = !(emailFromStore?.length > 0); vm.showSaveProgressModal = !hasSaveProgressContactInStore(
store.getters.order?.customer
);
if (store.getters.externalParameterState?.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) { if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction( await baseMixin.methods.dispatchStoreAction(

View file

@ -26,6 +26,7 @@
// Components // Components
import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/questions-page-layout"; import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/questions-page-layout";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question"; import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
import { hasSaveProgressContactInStore } from "@/helpers/save-progress-popup-contact-helper";
// Supporting Files // Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -59,12 +60,12 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const emailFromStore = store.getters.order.customer.emailAddress;
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.showSaveProgressModal = !(emailFromStore?.length > 0); vm.showSaveProgressModal = !hasSaveProgressContactInStore(
store.getters.order?.customer
);
if (store.getters.externalParameterState?.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) { if (store.getters.externalParameterServiceZip.zipCode) {
const serviceState = store.getters.order.serviceLocation.state; const serviceState = store.getters.order.serviceLocation.state;

View file

@ -48,6 +48,7 @@ import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import navbar from "@/fmg-components/nav-bar/nav-bar"; import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header"; import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question"; import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
import { hasSaveProgressContactInStore } from "@/helpers/save-progress-popup-contact-helper";
import { Form } from "vee-validate"; import { Form } from "vee-validate";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -91,8 +92,7 @@ export default {
}, },
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const emailFromStore = store.getters.order.customer.emailAddress; const showSaveProgressModal = !hasSaveProgressContactInStore(store.getters.order?.customer);
const showSaveProgressModal = !(emailFromStore?.length > 0);
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.clientConfig = resultMap.clientConfig ?? {}; vm.clientConfig = resultMap.clientConfig ?? {};

View file

@ -53,6 +53,7 @@ import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-heade
import navbar from "@/fmg-components/nav-bar/nav-bar"; import navbar from "@/fmg-components/nav-bar/nav-bar";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question"; import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
import { hasSaveProgressContactInStore } from "@/helpers/save-progress-popup-contact-helper";
// Supporting Files // Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
@ -77,11 +78,12 @@ export default {
}, },
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const emailFromStore = store.getters.order?.customer?.emailAddress;
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.showSaveProgressModal = !(emailFromStore?.length > 0); vm.showSaveProgressModal = !hasSaveProgressContactInStore(
store.getters.order?.customer
);
// Glass Part Question dynamic component // Glass Part Question dynamic component
Object.keys(vm.$refs) Object.keys(vm.$refs)