From 4b7a69f3318d773e74ea124da393fea2e5991a89 Mon Sep 17 00:00:00 2001 From: somramnani Date: Fri, 5 Jun 2026 12:56:23 -0400 Subject: [PATCH 1/6] som_fix(email-management): extract tenary operation into an independent statement --- .../email-sender/IntegratedEmailSender.jsx | 246 +++++++++--------- 1 file changed, 130 insertions(+), 116 deletions(-) diff --git a/src/components/EmailManagement/email-sender/IntegratedEmailSender.jsx b/src/components/EmailManagement/email-sender/IntegratedEmailSender.jsx index b88cd42a46..6e2e2975ef 100644 --- a/src/components/EmailManagement/email-sender/IntegratedEmailSender.jsx +++ b/src/components/EmailManagement/email-sender/IntegratedEmailSender.jsx @@ -112,6 +112,135 @@ const VariableRow = React.memo( } }, [onImageLoadStatusChange, variable?.name]); + const invalidImageUrlMessage = youtubeId + ? `The YouTube video "${youtubeId}" could not be loaded. Please verify the video exists and is publicly accessible.` + : 'The provided URL is not a valid image or YouTube video. Please provide a direct image URL (.jpg, .png, .gif) or a valid YouTube link.'; + const defaultInputPlaceholder = + variable.type === 'number' ? 'Enter number' : `Enter ${variable.name.toLowerCase()}`; + + let variableInput; + + if (variable.type === 'textarea') { + variableInput = ( + onVariableChange(variable.name, e.target.value)} + placeholder={`Enter ${variable.name.toLowerCase()}`} + invalid={!!error} + className="variable-input variable-textarea" + /> + ); + } else if (variable.type === 'image') { + variableInput = ( +
+ onImageSourceChange(variable.name, e.target.value)} + placeholder="Image URL or YouTube link" + invalid={!!error} + className="variable-input" + /> + + Supports: Direct image URLs (.jpg, .png, .gif, .webp, .svg), YouTube links + {extractedValue && (Auto-extracted)} + + {(value || extractedValue) && ( +
+ Preview: +
+ {!imgError ? ( + Preview + ) : ( + +
+ +
+ Invalid Image/Video URL +
+ {invalidImageUrlMessage} +
+
+
+
+ )} + {extractedValue && ( +
+
+ Extracted from: +
+
+ {value} +
+
+ )} +
+
+ )} +
+ ); + } else if (variable.type === 'url') { + variableInput = ( +
+ onVariableChange(variable.name, e.target.value)} + placeholder="https://example.com" + invalid={!!error} + className="variable-input" + /> + {value && ( +
+ URL Preview: + + {value} + +
+ )} +
+ ); + } else { + variableInput = ( + onVariableChange(variable.name, e.target.value)} + placeholder={defaultInputPlaceholder} + invalid={!!error} + className="variable-input" + /> + ); + } + return ( @@ -126,122 +255,7 @@ const VariableRow = React.memo( - {variable.type === 'textarea' ? ( - onVariableChange(variable.name, e.target.value)} - placeholder={`Enter ${variable.name.toLowerCase()}`} - invalid={!!error} - className="variable-input variable-textarea" - /> - ) : variable.type === 'image' ? ( -
- onImageSourceChange(variable.name, e.target.value)} - placeholder="Image URL or YouTube link" - invalid={!!error} - className="variable-input" - /> - - Supports: Direct image URLs (.jpg, .png, .gif, .webp, .svg), YouTube links - {extractedValue && (Auto-extracted)} - - {(value || extractedValue) && ( -
- Preview: -
- {!imgError ? ( - Preview - ) : ( - -
- -
- Invalid Image/Video URL -
- {youtubeId - ? `The YouTube video "${youtubeId}" could not be loaded. Please verify the video exists and is publicly accessible.` - : 'The provided URL is not a valid image or YouTube video. Please provide a direct image URL (.jpg, .png, .gif) or a valid YouTube link.'} -
-
-
-
- )} - {extractedValue && ( -
-
- Extracted from: -
-
- {value} -
-
- )} -
-
- )} -
- ) : variable.type === 'url' ? ( -
- onVariableChange(variable.name, e.target.value)} - placeholder="https://example.com" - invalid={!!error} - className="variable-input" - /> - {value && ( -
- URL Preview: - - {value} - -
- )} -
- ) : ( - onVariableChange(variable.name, e.target.value)} - placeholder={ - variable.type === 'number' ? 'Enter number' : `Enter ${variable.name.toLowerCase()}` - } - invalid={!!error} - className="variable-input" - /> - )} + {variableInput} {error &&
{error}
} From 4fae9b4a4c431fe181b17800ffaa2b9c64cbb74a Mon Sep 17 00:00:00 2001 From: somramnani Date: Fri, 5 Jun 2026 13:28:51 -0400 Subject: [PATCH 2/6] som_refactor(email-management): refactor function to reduce its cognitive complexity --- .../email-sender/WeeklyUpdateComposer.jsx | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx b/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx index 5105b4b434..3f92106934 100644 --- a/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx +++ b/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx @@ -275,6 +275,32 @@ const WeeklyUpdateComposer = ({ onClose }) => { [extractYouTubeId], ); + const getRecipientValidationResult = useCallback( + recipientText => { + if (!recipientText.trim()) { + return { error: 'Please enter at least one recipient', recipientEmails: [] }; + } + + const recipientEmails = parseRecipients(recipientText); + + if (recipientEmails.length === 0) { + return { error: 'Please enter at least one valid email address', recipientEmails }; + } + + const invalidEmails = recipientEmails.filter(email => !validateEmail(email)); + + if (invalidEmails.length > 0) { + return { + error: `Invalid email addresses: ${invalidEmails.join(', ')}`, + recipientEmails, + }; + } + + return { error: '', recipientEmails }; + }, + [parseRecipients, validateEmail], + ); + // Validate form const validateForm = useCallback(() => { const errors = {}; @@ -299,19 +325,12 @@ const WeeklyUpdateComposer = ({ onClose }) => { // Validate recipients for specific distribution if (emailDistribution === 'specific') { - if (!recipients.trim()) { - errors.recipients = 'Please enter at least one recipient'; + const { error, recipientEmails } = getRecipientValidationResult(recipients); + + if (error) { + errors.recipients = error; } else { - const recipientEmails = parseRecipients(recipients); - if (recipientEmails.length === 0) { - errors.recipients = 'Please enter at least one valid email address'; - } else { - const invalidEmails = recipientEmails.filter(email => !validateEmail(email)); - if (invalidEmails.length > 0) { - errors.recipients = `Invalid email addresses: ${invalidEmails.join(', ')}`; - } - setRecipientList(recipientEmails); - } + setRecipientList(recipientEmails); } } @@ -324,8 +343,7 @@ const WeeklyUpdateComposer = ({ onClose }) => { closingParagraph, emailDistribution, recipients, - parseRecipients, - validateEmail, + getRecipientValidationResult, extractYouTubeId, ]); From 060140f9a16cf22929ba72e8dec1d4361f830eea Mon Sep 17 00:00:00 2001 From: somramnani Date: Thu, 11 Jun 2026 18:02:40 -0400 Subject: [PATCH 3/6] refactor(emailmanaagement): refactor validateForm function to reduce Cognitive Complexity --- .../email-sender/WeeklyUpdateComposer.jsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx b/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx index 3f92106934..ef948da93f 100644 --- a/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx +++ b/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx @@ -275,6 +275,17 @@ const WeeklyUpdateComposer = ({ onClose }) => { [extractYouTubeId], ); + const getYouTubeValidationError = useCallback( + url => { + if (!url.trim()) { + return 'YouTube link is required'; + } + + return extractYouTubeId(url) ? '' : 'Please enter a valid YouTube URL'; + }, + [extractYouTubeId], + ); + const getRecipientValidationResult = useCallback( recipientText => { if (!recipientText.trim()) { @@ -313,10 +324,9 @@ const WeeklyUpdateComposer = ({ onClose }) => { errors.introParagraph = 'Intro paragraph is required'; } - if (!youtubeLink.trim()) { - errors.youtubeLink = 'YouTube link is required'; - } else if (!extractYouTubeId(youtubeLink)) { - errors.youtubeLink = 'Please enter a valid YouTube URL'; + const youtubeError = getYouTubeValidationError(youtubeLink); + if (youtubeError) { + errors.youtubeLink = youtubeError; } if (!closingParagraph.trim()) { @@ -344,7 +354,7 @@ const WeeklyUpdateComposer = ({ onClose }) => { emailDistribution, recipients, getRecipientValidationResult, - extractYouTubeId, + getYouTubeValidationError, ]); // Generate HTML email content - using CSS classes From 89b8f4d169f002e14098b2b012c2a601ce883f76 Mon Sep 17 00:00:00 2001 From: somramnani Date: Thu, 11 Jun 2026 18:15:27 -0400 Subject: [PATCH 4/6] fix issue in emailTemplateAction.js --- src/actions/emailTemplateActions.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/actions/emailTemplateActions.js b/src/actions/emailTemplateActions.js index c2d0e5af85..36421cca90 100644 --- a/src/actions/emailTemplateActions.js +++ b/src/actions/emailTemplateActions.js @@ -41,6 +41,8 @@ export const EMAIL_TEMPLATE_ACTIONS = { CLEAR_CURRENT_TEMPLATE: 'CLEAR_CURRENT_TEMPLATE', }; +const createEmailTemplateError = (message, details = {}) => Object.assign(new Error(message), details); + // Action Creators // Fetch all email templates with pagination and sorting @@ -268,7 +270,10 @@ export const previewEmailTemplate = (id, variables = {}) => async (dispatch, get payload: { message: errorMessage, errors: errorDetails, missing: missingVariables }, }); - throw { message: errorMessage, errors: errorDetails, missing: missingVariables }; + throw createEmailTemplateError(errorMessage, { + errors: errorDetails, + missing: missingVariables, + }); } }; @@ -318,6 +323,6 @@ export const validateEmailTemplate = id => async (dispatch, getState) => { payload: { message: errorMessage, errors: errorDetails }, }); - throw { message: errorMessage, errors: errorDetails }; + throw createEmailTemplateError(errorMessage, { errors: errorDetails }); } }; From d8dd473caf89a2f359a886b03682d8a0dd9f0543 Mon Sep 17 00:00:00 2001 From: somramnani Date: Fri, 12 Jun 2026 12:15:57 -0400 Subject: [PATCH 5/6] refactor(validation.js): fix cognitive complexity errors --- .../email-sender/validation.js | 147 ++++++++---------- 1 file changed, 63 insertions(+), 84 deletions(-) diff --git a/src/components/EmailManagement/email-sender/validation.js b/src/components/EmailManagement/email-sender/validation.js index 505cc048fc..25e31e3e62 100644 --- a/src/components/EmailManagement/email-sender/validation.js +++ b/src/components/EmailManagement/email-sender/validation.js @@ -58,102 +58,81 @@ function isValidImage(value, extractedValue) { return false; } -export function validateTemplateVariables(template, variableValues) { - const errors = {}; - - if (!template || !Array.isArray(template.variables) || template.variables.length === 0) { - return errors; - } - - template.variables.forEach(variable => { - if (!variable || !variable.name) return; - - const name = variable.name; - const type = variable.type || 'text'; - const required = variable.required !== undefined ? !!variable.required : true; - - const rawValue = variableValues?.[name]; - const extracted = variableValues?.[`${name}_extracted`]; - - // If not required and empty, skip validation - const hasAnyValue = isNonEmptyString(rawValue) || isNonEmptyString(extracted); - if (!required && !hasAnyValue) return; - - switch (type) { - case 'image': { - if (!isValidImage(rawValue, extracted)) { - errors[name] = required - ? `${name} is required (valid image URL or YouTube link)` - : `Please enter a valid image URL or YouTube link`; - } - break; - } - case 'url': { - if (!isValidUrl(rawValue)) { - errors[name] = required ? `${name} is required (valid URL)` : `Please enter a valid URL`; - } - break; - } - case 'number': { - if (!isValidNumber(rawValue)) { - errors[name] = required ? `${name} is required (number)` : `Please enter a valid number`; - } - break; - } - case 'textarea': - case 'text': - default: { - if (!isNonEmptyString(rawValue)) { - errors[name] = required ? `${name} is required` : `Please enter a value`; - } - break; - } - } - }); +const TEXT_VALIDATION_RULE = { + isValid: ({ rawValue }) => isNonEmptyString(rawValue), + requiredMessage: name => `${name} is required`, + optionalMessage: 'Please enter a value', +}; - return errors; -} +const VARIABLE_VALIDATION_RULES = { + image: { + isValid: ({ rawValue, extracted }) => isValidImage(rawValue, extracted), + requiredMessage: name => `${name} is required (valid image URL or YouTube link)`, + optionalMessage: 'Please enter a valid image URL or YouTube link', + }, + url: { + isValid: ({ rawValue }) => isValidUrl(rawValue), + requiredMessage: name => `${name} is required (valid URL)`, + optionalMessage: 'Please enter a valid URL', + }, + number: { + isValid: ({ rawValue }) => isValidNumber(rawValue), + requiredMessage: name => `${name} is required (number)`, + optionalMessage: 'Please enter a valid number', + }, + textarea: TEXT_VALIDATION_RULE, + text: TEXT_VALIDATION_RULE, +}; -export function validateVariable(variable, variableValues) { +function getVariableValidationContext(variable, variableValues) { if (!variable || !variable.name) return null; + const name = variable.name; - const type = variable.type || 'text'; const required = variable.required !== undefined ? !!variable.required : true; - const rawValue = variableValues?.[name]; const extracted = variableValues?.[`${name}_extracted`]; const hasAnyValue = isNonEmptyString(rawValue) || isNonEmptyString(extracted); if (!required && !hasAnyValue) return null; - switch (type) { - case 'image': - return isValidImage(rawValue, extracted) - ? null - : required - ? `${name} is required (valid image URL or YouTube link)` - : 'Please enter a valid image URL or YouTube link'; - case 'url': - return isValidUrl(rawValue) - ? null - : required - ? `${name} is required (valid URL)` - : 'Please enter a valid URL'; - case 'number': - return isValidNumber(rawValue) - ? null - : required - ? `${name} is required (number)` - : 'Please enter a valid number'; - case 'textarea': - case 'text': - default: - return isNonEmptyString(rawValue) - ? null - : required - ? `${name} is required` - : 'Please enter a value'; + return { + name, + type: variable.type || 'text', + required, + rawValue, + extracted, + }; +} + +function getVariableValidationError(context) { + const rule = VARIABLE_VALIDATION_RULES[context.type] || TEXT_VALIDATION_RULE; + + if (rule.isValid(context)) return null; + + return context.required ? rule.requiredMessage(context.name) : rule.optionalMessage; +} + +export function validateTemplateVariables(template, variableValues) { + if (!template || !Array.isArray(template.variables) || template.variables.length === 0) { + return {}; } + + return template.variables.reduce((errors, variable) => { + const context = getVariableValidationContext(variable, variableValues); + if (!context) return errors; + + const error = getVariableValidationError(context); + if (error) { + errors[context.name] = error; + } + + return errors; + }, {}); +} + +export function validateVariable(variable, variableValues) { + const context = getVariableValidationContext(variable, variableValues); + return context ? getVariableValidationError(context) : null; } export function extractImageForVariableIfNeeded(variable, variableValues) { From ff92edca6347eb16cc2b943bbc0a82d47baedb0a Mon Sep 17 00:00:00 2001 From: somramnani Date: Fri, 12 Jun 2026 12:34:52 -0400 Subject: [PATCH 6/6] refactor: fix contrast ratio issues across IntegratedEmailSender.module.css and EmailManagementShared.module.css --- .../EmailManagementShared.module.css | 4 +- .../IntegratedEmailSender.module.css | 44 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/EmailManagement/EmailManagementShared.module.css b/src/components/EmailManagement/EmailManagementShared.module.css index f9696805c1..876d00404a 100644 --- a/src/components/EmailManagement/EmailManagementShared.module.css +++ b/src/components/EmailManagement/EmailManagementShared.module.css @@ -468,7 +468,7 @@ } .dark-mode .btn-outline-primary:hover { - background-color: #4a9eff; + background-color: #0056b3; color: white; } @@ -566,4 +566,4 @@ .stat-card:hover { transform: none; } -} \ No newline at end of file +} diff --git a/src/components/EmailManagement/email-sender/IntegratedEmailSender.module.css b/src/components/EmailManagement/email-sender/IntegratedEmailSender.module.css index 63eec88961..c23d4e988f 100644 --- a/src/components/EmailManagement/email-sender/IntegratedEmailSender.module.css +++ b/src/components/EmailManagement/email-sender/IntegratedEmailSender.module.css @@ -96,53 +96,53 @@ /* Mode button variants */ .mode-buttons .btn-primary { - background: #007bff; - border-color: #007bff; + background: #0056b3; + border-color: #0056b3; color: white; } .mode-buttons .btn-outline-primary { background: white; - color: #007bff; - border-color: #007bff; + color: #0056b3; + border-color: #0056b3; } .mode-buttons .btn-outline-primary:hover { - background: #007bff; + background: #0056b3; color: white; } .mode-buttons .btn-success { - background: #28a745; - border-color: #28a745; + background: #1e7e34; + border-color: #1e7e34; color: white; } .mode-buttons .btn-outline-success { background: white; - color: #28a745; - border-color: #28a745; + color: #1e7e34; + border-color: #1e7e34; } .mode-buttons .btn-outline-success:hover { - background: #28a745; + background: #1e7e34; color: white; } .mode-buttons .btn-info { - background: #17a2b8; - border-color: #17a2b8; + background: #0f6674; + border-color: #0f6674; color: white; } .mode-buttons .btn-outline-info { background: white; - color: #17a2b8; - border-color: #17a2b8; + color: #0f6674; + border-color: #0f6674; } .mode-buttons .btn-outline-info:hover { - background: #17a2b8; + background: #0f6674; color: white; } @@ -906,9 +906,9 @@ body.dark-mode .alert-warning * { } .dark-mode .mode-buttons .btn-outline-primary:hover { - background: #3182ce; + background: #0056b3; color: white; - border-color: #3182ce; + border-color: #0056b3; } .dark-mode .mode-buttons .btn-outline-success { @@ -918,9 +918,9 @@ body.dark-mode .alert-warning * { } .dark-mode .mode-buttons .btn-outline-success:hover { - background: #38a169; + background: #1e7e34; color: white; - border-color: #38a169; + border-color: #1e7e34; } .dark-mode .mode-buttons .btn-outline-info { @@ -930,9 +930,9 @@ body.dark-mode .alert-warning * { } .dark-mode .mode-buttons .btn-outline-info:hover { - background: #17a2b8; + background: #0f6674; color: white; - border-color: #17a2b8; + border-color: #0f6674; } .dark-mode .action-buttons .btn-outline-secondary { @@ -1177,4 +1177,4 @@ body.dark-mode .alert-warning * { .draft-notification .d-flex.gap-2 button { flex: 1; } -} \ No newline at end of file +}