From 7e61007212508ec0e2260f7163bea2ba83f799d2 Mon Sep 17 00:00:00 2001 From: AndyEPhipps Date: Fri, 7 Nov 2025 11:11:52 +0000 Subject: [PATCH 1/4] feat: functionality to handle soft email opt-ins --- .../MarketingPreferencesDSForm.js | 13 +++++++++++-- .../_MarketingPreferencesDS.js | 19 ++++++++++++++----- .../_MarketingPrefsConfig.js | 4 ++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js b/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js index 99834eaab..a0d941171 100644 --- a/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js +++ b/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js @@ -1,4 +1,5 @@ -import React from 'react'; +/* eslint-disable no-console */ +import React, { useState } from 'react'; import { useForm, FormProvider } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import Text from '../../Atoms/Text/Text'; @@ -41,9 +42,12 @@ const { } = mpValidationCustom; const MarketingPreferencesDSForm = () => { + const [emailInteractedWith, setEmailInteractedWith] = useState(false); + function customSubmitHandler(formData) { - // eslint-disable-next-line no-console console.log('Successful submission', formData); + // And we'd do something with this in a real context: + console.log('emailInteractedWith:', emailInteractedWith); } // For our default instance: @@ -71,6 +75,8 @@ const MarketingPreferencesDSForm = () => { mpValidationOptions={mpValidationOptions} id="default" formContext={formMethods} + // Pass in our useState function as the callback directly: + emailChoiceCallback={setEmailInteractedWith} /> @@ -85,6 +91,9 @@ const MarketingPreferencesDSForm = () => { mpValidationOptions={mpValidationOptionsCustom} id="custom" formContext={formMethodsCustom} + // Pass in our useState function as the callback directly: + emailChoiceCallback={setEmailInteractedWith} + /> diff --git a/src/components/Organisms/MarketingPreferencesDS/_MarketingPreferencesDS.js b/src/components/Organisms/MarketingPreferencesDS/_MarketingPreferencesDS.js index 33e8a34e2..eabd13f36 100644 --- a/src/components/Organisms/MarketingPreferencesDS/_MarketingPreferencesDS.js +++ b/src/components/Organisms/MarketingPreferencesDS/_MarketingPreferencesDS.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { useWatch } from 'react-hook-form'; import _ from 'lodash'; @@ -20,6 +20,7 @@ const MarketingPreferencesDS = ({ mpValidationOptions, id = null, formContext = null, + emailChoiceCallback, ...rest }) => { const { formState: { errors }, control } = formContext; @@ -30,6 +31,15 @@ const MarketingPreferencesDS = ({ const phoneChoice = useWatch({ control, name: 'mp_permissionPhone', defaultValue: null }); const smsChoice = useWatch({ control, name: 'mp_permissionSMS', defaultValue: null }); + // NB: this presumes we're still not setting this to option 'Yes' by default): + useEffect(() => { + // Don't run on initial mount, only when actually updated: + if (emailChoice !== null) { + // Fire callback to update flag in parent component: + emailChoiceCallback(true); + } + }, [emailChoice, emailChoiceCallback]); + const { // eslint-disable-next-line camelcase mp_permissionEmail, mp_permissionSMS, mp_permissionPhone, mp_permissionPost @@ -37,11 +47,8 @@ const MarketingPreferencesDS = ({ // If the field is not required for each No/Yes choice, remove it from the DOM entirely const disableEmailInput = (mp_permissionEmail.yes === false && emailChoice.includes('yes')); - const disableSMSInput = (mp_permissionSMS.yes === false && smsChoice.includes('yes')); - const disablePhoneInput = (mp_permissionPhone.yes === false && phoneChoice.includes('yes')); - const disablePostInput = (mp_permissionPost.yes === false && postChoice.includes('yes')); // Required to track multiple errors to determine whether to show/hide the fieldset @@ -273,7 +280,9 @@ MarketingPreferencesDS.propTypes = { in the parent to set-up the validation, but also required here for additional functionality */ mpValidationOptions: PropTypes.objectOf(PropTypes.shape).isRequired, id: PropTypes.string, - formContext: PropTypes.shape() + formContext: PropTypes.shape(), + // For soft opt-in, as react-hook-form's 'isDirty' doesn't work with checkboxes, only Fields 😮‍💨 + emailChoiceCallback: PropTypes.func }; MaybeDisabled.propTypes = { diff --git a/src/components/Organisms/MarketingPreferencesDS/_MarketingPrefsConfig.js b/src/components/Organisms/MarketingPreferencesDS/_MarketingPrefsConfig.js index dc9ec76bd..555a29550 100644 --- a/src/components/Organisms/MarketingPreferencesDS/_MarketingPrefsConfig.js +++ b/src/components/Organisms/MarketingPreferencesDS/_MarketingPrefsConfig.js @@ -1,6 +1,8 @@ import * as yup from 'yup'; import { merge } from 'lodash'; +const phoneRegex = /^(((((\+44)|(0044))\s?\d{4}|\(?0\d{4}\)?)\s?\d{3}\s?\d{3})|((((\+44)|(0044))\s?\d{3}|\(?0\d{3}\)?)\s?\d{3}\s?\d{4})|((((\+44)|(0044))\s?\d{2}|\(?0\d{2}\)?)\s?\d{4}\s?\d{4}))(\s?\\#(\d{4}|\d{3}))?$/; + const setInitialValues = overrideValues => { const defaultValues = { mp_email: '', @@ -61,8 +63,6 @@ const buildValidationSchema = overrideOptions => { // Override with any custom options supplied const mpValidationOptions = merge(defaultOptions, overrideOptions); - const phoneRegex = /^(((((\+44)|(0044))\s?\d{4}|\(?0\d{4}\)?)\s?\d{3}\s?\d{3})|((((\+44)|(0044))\s?\d{3}|\(?0\d{3}\)?)\s?\d{3}\s?\d{4})|((((\+44)|(0044))\s?\d{2}|\(?0\d{2}\)?)\s?\d{4}\s?\d{4}))(\s?\\#(\d{4}|\d{3}))?$/; - const mpValidationFields = { mp_email: yup.string() .when('mp_permissionEmail', { From 597888d2b7d14365a35798b170b4f80a1ea1b9bf Mon Sep 17 00:00:00 2001 From: AndyEPhipps Date: Fri, 7 Nov 2025 11:18:08 +0000 Subject: [PATCH 2/4] Improve naming --- .../MarketingPreferencesDS/MarketingPreferencesDSForm.js | 4 ++-- .../MarketingPreferencesDS/_MarketingPreferencesDS.js | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js b/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js index a0d941171..120363169 100644 --- a/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js +++ b/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js @@ -76,7 +76,7 @@ const MarketingPreferencesDSForm = () => { id="default" formContext={formMethods} // Pass in our useState function as the callback directly: - emailChoiceCallback={setEmailInteractedWith} + emailInteractedCallback={setEmailInteractedWith} /> @@ -92,7 +92,7 @@ const MarketingPreferencesDSForm = () => { id="custom" formContext={formMethodsCustom} // Pass in our useState function as the callback directly: - emailChoiceCallback={setEmailInteractedWith} + emailInteractedCallback={setEmailInteractedWith} /> diff --git a/src/components/Organisms/MarketingPreferencesDS/_MarketingPreferencesDS.js b/src/components/Organisms/MarketingPreferencesDS/_MarketingPreferencesDS.js index eabd13f36..b62aeae3e 100644 --- a/src/components/Organisms/MarketingPreferencesDS/_MarketingPreferencesDS.js +++ b/src/components/Organisms/MarketingPreferencesDS/_MarketingPreferencesDS.js @@ -20,7 +20,7 @@ const MarketingPreferencesDS = ({ mpValidationOptions, id = null, formContext = null, - emailChoiceCallback, + emailInteractedCallback, ...rest }) => { const { formState: { errors }, control } = formContext; @@ -31,14 +31,13 @@ const MarketingPreferencesDS = ({ const phoneChoice = useWatch({ control, name: 'mp_permissionPhone', defaultValue: null }); const smsChoice = useWatch({ control, name: 'mp_permissionSMS', defaultValue: null }); - // NB: this presumes we're still not setting this to option 'Yes' by default): useEffect(() => { // Don't run on initial mount, only when actually updated: if (emailChoice !== null) { // Fire callback to update flag in parent component: - emailChoiceCallback(true); + emailInteractedCallback(true); } - }, [emailChoice, emailChoiceCallback]); + }, [emailChoice, emailInteractedCallback]); const { // eslint-disable-next-line camelcase @@ -282,7 +281,7 @@ MarketingPreferencesDS.propTypes = { id: PropTypes.string, formContext: PropTypes.shape(), // For soft opt-in, as react-hook-form's 'isDirty' doesn't work with checkboxes, only Fields 😮‍💨 - emailChoiceCallback: PropTypes.func + emailInteractedCallback: PropTypes.func }; MaybeDisabled.propTypes = { From 4d3f3756fd45bf2da95c59a0af4461df4a8d5a93 Mon Sep 17 00:00:00 2001 From: AndyEPhipps Date: Tue, 11 Nov 2025 11:37:45 +0000 Subject: [PATCH 3/4] Trying config --- .../MarketingPreferencesDS/MarketingPreferencesDSForm.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js b/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js index 120363169..2a6a0aa31 100644 --- a/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js +++ b/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js @@ -24,7 +24,8 @@ const { mpValidationSchema, mpValidationOptions } = mpValidation; // Or customise and override the config to suit the destination app's requirements: const initalValueOverrides = { - mp_email: 'user@website.com' // Potentially provided earlier in the journey + mp_email: 'user@website.com', // Potentially provided earlier in the journey + mp_permissionEmail: 'true' }; const validationOverrides = { mp_permissionEmail: { hideInput: true }, // As we're passing a value above, hide the user input From 17dd39b52c73defaeba11f668d8244fc1848412b Mon Sep 17 00:00:00 2001 From: AndyEPhipps Date: Tue, 11 Nov 2025 14:32:18 +0000 Subject: [PATCH 4/4] Clarify comments --- .../MarketingPreferencesDS/MarketingPreferencesDSForm.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js b/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js index 2a6a0aa31..f3f9c1ebc 100644 --- a/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js +++ b/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDSForm.js @@ -46,8 +46,8 @@ const MarketingPreferencesDSForm = () => { const [emailInteractedWith, setEmailInteractedWith] = useState(false); function customSubmitHandler(formData) { + // Obviously, in a *real* context, we'd do something useful with this data: console.log('Successful submission', formData); - // And we'd do something with this in a real context: console.log('emailInteractedWith:', emailInteractedWith); } @@ -76,7 +76,7 @@ const MarketingPreferencesDSForm = () => { mpValidationOptions={mpValidationOptions} id="default" formContext={formMethods} - // Pass in our useState function as the callback directly: + // Directly pass in our useState 'set' function as the callback: emailInteractedCallback={setEmailInteractedWith} /> @@ -92,9 +92,8 @@ const MarketingPreferencesDSForm = () => { mpValidationOptions={mpValidationOptionsCustom} id="custom" formContext={formMethodsCustom} - // Pass in our useState function as the callback directly: + // Directly pass in our useState 'set' function as the callback: emailInteractedCallback={setEmailInteractedWith} - />