diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index 4326a5412..45841f21f 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -18,6 +18,8 @@ const errorMessages = { LAST_NAME_REQUIRED: "Please enter your last name", EMAIL_ADDRESS_REQUIRED: "Please enter your email address", EMAIL_ADDRESS_FORMAT: "Please enter a valid email address", + EMAIL_SMS_REQUIRED: "Please enter your mobile phone number or email", + EMAIL_SMS_FORMAT: "Please enter a valid mobile phone number or email", SERVICE_ZIP_REQUIRED: "Please enter your service ZIP", SERVICE_ZIP_FORMAT: "Please enter a valid service ZIP", VIN_REQUIRED: "Please enter your VIN", diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index ec6b0db0c..9ad6d9890 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -1,6 +1,7 @@ const storeActions = { // Content Actions GET_PAGE_DATA: "getPageData", + SAVE_PAGE_DATA: "savePageData", // Vehicle Actions GET_VEHICLE_YEARS: "getVehicleYears", @@ -54,7 +55,7 @@ const storeActions = { GET_RECAL_PARTS_AND_SAVE_TO_LINE_ITEMS: "getRecalPartsAndSaveToLineItems", // Analytics - LOG_EXPERIMENT_EXPOSURE: "logExperimentExposure", + LOG_EXPERIMENT_EXPOSURE_AND_UPDATE_STORE: "logExperimentExposureAndUpdateStore", LOG_PAGE_VIEW: "logPageView", LOG_CUSTOM_EVENT: "logCustomEvent", INITIALIZE_SESSION: "initializeSession", diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index 0739a9af6..804ed5608 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -121,9 +121,9 @@ export default { const partsOrQuestions = orderFromStore.damage?.partQuestionAnswers; if (partsOrQuestions) { - partsOrQuestions.forEach(pqa => { + partsOrQuestions.forEach((pqa) => { const payloadPartQuestions = []; - pqa.answeredQuestions.forEach(answer => { + pqa.answeredQuestions.forEach((answer) => { payloadPartQuestions.push({ questionSeq: answer?.questionNum, questionText: answer?.questionText, diff --git a/src/layouts/service-zip/service-zip.spec.js b/src/layouts/service-zip/service-zip.spec.js index 214fd96f0..773157980 100644 --- a/src/layouts/service-zip/service-zip.spec.js +++ b/src/layouts/service-zip/service-zip.spec.js @@ -181,6 +181,7 @@ function applyMockStoreDataToGetters() { payment: mockStoreData.payment, policy: mockStoreData.policy, externalParameterServiceZip: mockStoreData.externalParameterServiceZip, + emailOrSms: mockStoreData.customer.emailAddress, }; store.state.order = mockStoreData; store.state.applicationUser.experiments = mockExperimentSettings; @@ -241,7 +242,7 @@ describe("service-zip.vue", () => { // Assert expect(wrapper.vm.serviceZipCode).toEqual("11111"); - expect(wrapper.vm.emailAddress).toEqual("builddigitaltest@safelite.com"); + expect(wrapper.vm.emailOrSms).toEqual("builddigitaltest@safelite.com"); }); test("Zip in querystring -> pushed to data", () => { diff --git a/src/layouts/service-zip/service-zip.vue b/src/layouts/service-zip/service-zip.vue index b74db95dc..18a8b3362 100644 --- a/src/layouts/service-zip/service-zip.vue +++ b/src/layouts/service-zip/service-zip.vue @@ -23,11 +23,11 @@ @@ -88,16 +88,17 @@ import vinPagesMixin from "@/mixins/vin-pages-mixin"; import { saveSession } from "@/helpers/heritage-integration/order-helper.js"; import baseMixin from "@/mixins/base-mixin.js"; import { queryStrings } from "@/constants/query-strings"; +import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; // Define Validation Rules defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT)); -defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED)); +defineRule("email-sms-required", required(errorMessages.EMAIL_SMS_REQUIRED)); defineRule( - "email-address-format", + "email-sms-format", regex( - /^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/, - errorMessages.EMAIL_ADDRESS_FORMAT + /^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$|^\(?(\d{3})\)?[-. ]?(\d{3})[-. ]?(\d{4})$/, + errorMessages.EMAIL_SMS_FORMAT ) ); defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); @@ -108,7 +109,7 @@ export default { data() { return { serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode, - emailAddress: this.getEmailFromStore(), + emailOrSms: this.getEmailOrSmsFromStore(), displayInvalidZipAlert: false, displayNonServiceableZipAlert: false, }; @@ -160,11 +161,11 @@ export default { store.getters.order.serviceLocation.zipCode ); }, - getEmailFromStore() { - return ( - store.getters.externalParameterServiceZip.emailAddress ?? - store.getters.order.customer.emailAddress - ); + getEmailOrSmsFromStore() { + return store.getters.emailOrSms; + }, + getPhoneFromStore() { + return store.getters.order.customer.phoneNumber; }, arePagePrerequisitesValid() { return store.getters.damage.isRepair || store.getters.damage.glassToReplace?.length > 0; @@ -186,7 +187,23 @@ export default { }, async forwardButtonAction() { const zipCodeData = await this.getZipCodeData(this.serviceZipCode); - await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false); + + if (vinPagesMixin.methods.isPhoneNumber(this.emailOrSms)) { + const phone = this.emailOrSms.replace(/[()]/g, ""); + await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, phone, false); + } else { + await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailOrSms, false); + } + + await this.dispatchStoreAction( + storeActions.SAVE_PAGE_DATA, + { + page: fmgPageValues.EMAIL_SMS_PAGES, + data: { emailOrSmsValue: this.emailOrSms }, + }, + false + ); + await this.dispatchStoreAction( storeActions.SAVE_SERVICE_ZIP_CODE_INFO, { diff --git a/src/layouts/vehicle/vehicle.vue b/src/layouts/vehicle/vehicle.vue index 728e1dcfd..12e7268fa 100644 --- a/src/layouts/vehicle/vehicle.vue +++ b/src/layouts/vehicle/vehicle.vue @@ -186,7 +186,7 @@ export default { if (experimentForLogging !== undefined) { // Log experiment exposure baseMixin.methods.dispatchStoreActionWithLogging( - storeActions.LOG_EXPERIMENT_EXPOSURE, + storeActions.LOG_EXPERIMENT_EXPOSURE_AND_UPDATE_STORE, { userId: getUserIdValue(), deviceId: getDeviceIdValue(), diff --git a/src/mixins/vin-pages-mixin.js b/src/mixins/vin-pages-mixin.js index 7d15066c5..fc195d910 100644 --- a/src/mixins/vin-pages-mixin.js +++ b/src/mixins/vin-pages-mixin.js @@ -16,6 +16,11 @@ export default { ? "email-address-format" : "email-address-required|email-address-format"; }, + EmailOrSmsValidationRules() { + return this.IsEmailOptional + ? "email-sms-format" + : "email-sms-required|email-sms-format"; + }, }, methods: { async navigateForwardWithSingleCarMatch() { @@ -56,5 +61,9 @@ export default { false ); }, + isPhoneNumber(input) { + const phoneRegex = /^\(?(\d{3})\)?[-. ]?(\d{3})[-. ]?(\d{4})$/; + return phoneRegex.test(input); + }, }, }; diff --git a/src/router/router-constants/fmgPage-values.js b/src/router/router-constants/fmgPage-values.js index 5fade6506..1cba0414b 100644 --- a/src/router/router-constants/fmgPage-values.js +++ b/src/router/router-constants/fmgPage-values.js @@ -23,6 +23,7 @@ const fmgPageValues = { PAYMENT_PIA_RETURN: "payment-pia-return", CONFIRMATION: "confirmation", RETURN_USER: "return-user", + EMAIL_SMS_PAGES: "email-or-sms-related-pages", }; const funnelStartPageName = fmgPageValues.VEHICLE; diff --git a/src/store/index.js b/src/store/index.js index c07fffbd3..1d409af98 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -670,34 +670,38 @@ export const mutations = { sessionInformation.order.payment.billToAccountNumber; state.order.payment.inactivePromos = sessionInformation.order.payment.inactivePromos; - state.order.serviceLocation.address = - sessionInformation.order.serviceLocation.streetAddress; - state.order.serviceLocation.address2 = - sessionInformation.order.serviceLocation.streetAddress2; - state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city; - state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state; - state.order.serviceLocation.zipCode = sessionInformation.order.serviceLocation.zipCode; - state.order.serviceLocation.zipCodeCtu = - sessionInformation.order.serviceLocation.zipCodeCtu; + //Do not assign default service type once user make service type selection + if (state.order.serviceLocation.appointmentType == null) { + state.order.serviceLocation.address = + sessionInformation.order.serviceLocation.streetAddress; + state.order.serviceLocation.address2 = + sessionInformation.order.serviceLocation.streetAddress2; + state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city; + state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state; + state.order.serviceLocation.zipCode = sessionInformation.order.serviceLocation.zipCode; + state.order.serviceLocation.zipCodeCtu = + sessionInformation.order.serviceLocation.zipCodeCtu; - state.order.serviceLocation.appointmentType = - sessionInformation.order.serviceLocation.appointmentType; - state.order.serviceLocation.isVehicleProtected = - sessionInformation.order.serviceLocation.isVehicleProtected; + state.order.serviceLocation.appointmentType = + sessionInformation.order.serviceLocation.appointmentType; + state.order.serviceLocation.isVehicleProtected = + sessionInformation.order.serviceLocation.isVehicleProtected; - state.order.serviceLocation.provider.providerNumber = - sessionInformation.order.serviceLocation.provider?.providerNumber; - state.order.serviceLocation.provider.address.streetAddress = - sessionInformation.order.serviceLocation.provider?.address?.streetAddress; - state.order.serviceLocation.provider.address.city = - sessionInformation.order.serviceLocation.provider?.address?.city; - state.order.serviceLocation.provider.address.state = - sessionInformation.order.serviceLocation.provider?.address?.state; - state.order.serviceLocation.provider.address.zipCode = - sessionInformation.order.serviceLocation.provider?.address?.zipCode; - state.order.serviceLocation.provider.address.zipCodeCtu = - sessionInformation.order.serviceLocation.provider?.address?.zipCodeCtu; - state.order.serviceLocation.techNotes = sessionInformation.order.serviceLocation.techNotes; + state.order.serviceLocation.provider.providerNumber = + sessionInformation.order.serviceLocation.provider?.providerNumber; + state.order.serviceLocation.provider.address.streetAddress = + sessionInformation.order.serviceLocation.provider?.address?.streetAddress; + state.order.serviceLocation.provider.address.city = + sessionInformation.order.serviceLocation.provider?.address?.city; + state.order.serviceLocation.provider.address.state = + sessionInformation.order.serviceLocation.provider?.address?.state; + state.order.serviceLocation.provider.address.zipCode = + sessionInformation.order.serviceLocation.provider?.address?.zipCode; + state.order.serviceLocation.provider.address.zipCodeCtu = + sessionInformation.order.serviceLocation.provider?.address?.zipCodeCtu; + state.order.serviceLocation.techNotes = + sessionInformation.order.serviceLocation.techNotes; + } // if we're loading a session and we do not have a provider number yet, then set isInsurance to null so that // a service package is not selected by default on the quote page. @@ -741,14 +745,15 @@ export const mutations = { state.applicationUser.savedSessionId = sessionInformation.applicationUser.savedSessionId; } - - state.order.schedule.date = sessionInformation.order.schedule?.date; - state.order.schedule.startTime = sessionInformation.order.schedule?.startTime; - state.order.schedule.endTime = sessionInformation.order.schedule?.endTime; - state.order.schedule.routeCode = sessionInformation.order.schedule?.routeCode; - state.order.schedule.jobMaxMinutes = sessionInformation.order.schedule?.jobMaxMinutes; - state.order.schedule.jobMinMinutes = sessionInformation.order.schedule?.jobMinMinutes; - + //Do not assign default schedule details once user make schedule selection + if (state.order.schedule.date == null) { + state.order.schedule.date = sessionInformation.order.schedule?.date; + state.order.schedule.startTime = sessionInformation.order.schedule?.startTime; + state.order.schedule.endTime = sessionInformation.order.schedule?.endTime; + state.order.schedule.routeCode = sessionInformation.order.schedule?.routeCode; + state.order.schedule.jobMaxMinutes = sessionInformation.order.schedule?.jobMaxMinutes; + state.order.schedule.jobMinMinutes = sessionInformation.order.schedule?.jobMinMinutes; + } state.order.policy.currentDeductible = sessionInformation.order.policy?.currentDeductible; state.order.policy.originalDeductible = sessionInformation.order.policy?.originalDeductible; state.order.policy.additionalAuthFlag = sessionInformation.order.policy?.additionalAuthFlag; @@ -989,6 +994,26 @@ export const getters = { externalParameterEstimate: (state) => externalParameterState?.estimate, externalParameterCustomer: (state) => externalParameterState?.customer, isExternalParameter: (state) => externalParameterState?.isExternalParameter, + + emailOrSms: (state) => { + const email = externalParameterState.serviceZip.emailAddress ?? state.order.customer.emailAddress; + const phone = state.order.customer.phoneNumber; + + if (!phone && !email) { + return null; + } + + if (phone && !email) { + return phone; + } + + if (email && !phone) { + return email; + } + + // if we're here, then we have both email and phone so get the value from pageData to reflect what they entered on the page + return state.applicationUser.pageData[fmgPageValues.EMAIL_SMS_PAGES]?.emailOrSmsValue; + }, }; function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { @@ -1254,10 +1279,23 @@ export const actions = { }, // Analytics Actions - logExperimentExposure( + logExperimentExposureAndUpdateStore( context, { payload: { userId, deviceId, sessionKey, pageName, experiment }, pageNameToLog } ) { + const experiments = deepClone(context.getters.applicationUser.experiments); + const experimentToMarkAsExposed = experiments.find(ex => { + ex.universeId == experiment.universeId && + ex.testId == experiment.testIde && + ex.variationId == experiment.variationId && + ex.assignmentId == experiment.assignmentId + }); + if (experimentToMarkAsExposed) { + experimentToMarkAsExposed.isExposed = true; + } + + context.commit(storeMutations.UPDATE_EXPERIMENTS, experiments); + return globalMethods.callHttpClient({ method: endpoints.LogExperimentExposureIfAssigned.method, endpoint: endpoints.LogExperimentExposureIfAssigned.url, @@ -2242,7 +2280,7 @@ export const actions = { response.data ); - await resetScheduleIfUnavailable(context, response.data.order, pageNameToLog); + await resetScheduleIfUnavailable(context, context.state.order, pageNameToLog); return response; }, (error) => { @@ -3015,7 +3053,14 @@ export const actions = { }, savePhoneNumber(context, phoneNumber) { - context.commit(storeMutations.UPDATE_CUSTOMER_PHONE_NUMBER, phoneNumber === "" ? null : phoneNumber); + context.commit( + storeMutations.UPDATE_CUSTOMER_PHONE_NUMBER, + phoneNumber === "" ? null : phoneNumber + ); + }, + + savePageData(context, emailOrSms) { + context.commit(storeMutations.UPDATE_PAGE_DATA, emailOrSms); }, saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) {