diff --git a/public/static/locales/en/bulkCodes.json b/public/static/locales/en/bulkCodes.json index 4b57c90d67..3912a80131 100644 --- a/public/static/locales/en/bulkCodes.json +++ b/public/static/locales/en/bulkCodes.json @@ -10,12 +10,20 @@ "importMethodText": { "title": "Import File", "subtitle": "Use this method if one of the following criteria match your use case:", - "details": ["I want to provide the Recipient's name or Email for each code.", "I want Plant-for-the-Planet to automatically email the recipients once it has been generated (optional).", "I want to issue codes for different tree counts."] + "details": [ + "I want to provide the Recipient's name or Email for each code.", + "I want Plant-for-the-Planet to automatically email the recipients once it has been generated (optional).", + "I want to issue codes for different tree counts." + ] }, "genericMethodText": { "title": "Create Generic Codes", "subtitle": "Use this method if the following criteria matches your use case:", - "details": ["All codes will have the same value.", "I want to generate a number of code for arbitrary recipients.", "Names and Emails cannot be associated with the code."] + "details": [ + "All codes will have the same value.", + "I want to generate a number of code for arbitrary recipients.", + "Names and Emails cannot be associated with the code." + ] }, "projectName": "Project Name", "costPerUnit": "Cost per Unit", @@ -31,6 +39,7 @@ "occasion": "Occasion", "total": "Total", "chargeConsentText": "By clicking Issue Codes, you agree that above amount will be charged to your account.", + "invalidEmailWarningText": "Please make sure that that recipient_email is a valid email. Using dummy or invalid emails will lead to account suspension in accordance with Platform terms and conditions.", "projectRequired": "No project is selected.", "unitsPerCodeRequired": "Each code should contain at least 1 unit.", "codeQuantityRequired": "You need to create at least 1 code.", @@ -61,7 +70,7 @@ "unitsNotProvided": "Units are missing/invalid for some recipients.", "notifyNotPossible": "Email and name are missing for some recipients that are to be notified.", "instructionRowError": "An error occurred on the 1st row. Please check if you have deleted the instructions, or if there is additional data.", - "invalidEmails": "The emails provided are invalid on the following rows: ", + "invalidEmails": "recipient_email contains dummy/invalid emails on the following rows: {{rowList}}. Please remove invalid emails or add valid emails.", "generalError": "Something went wrong. Please try after a while." }, "successUploadCSV": { @@ -75,4 +84,4 @@ "planet_cash_payment_failure": "The transaction failed. {{reason}}", "planet_cash_invalid_project": "You cannot make a donation to the selected project." } -} \ No newline at end of file +} diff --git a/src/features/user/BulkCodes/components/BulkGiftTotal.tsx b/src/features/user/BulkCodes/components/BulkGiftTotal.tsx index c5b0cac4a0..9e1fb257f6 100644 --- a/src/features/user/BulkCodes/components/BulkGiftTotal.tsx +++ b/src/features/user/BulkCodes/components/BulkGiftTotal.tsx @@ -10,6 +10,7 @@ interface BulkGiftTotalProps { currency?: string; units?: number; unit?: string; + isImport?: boolean; } const BulkGiftTotal = ({ @@ -17,6 +18,7 @@ const BulkGiftTotal = ({ currency, units = 0, unit = 'tree', + isImport = false, }: BulkGiftTotalProps): ReactElement | null => { const { t, ready, i18n } = useTranslation(['common', 'bulkCodes']); @@ -43,7 +45,17 @@ const BulkGiftTotal = ({ currency as string, amount )} for ${units} ${getUnit(unit, units)}`} - helperText={t('bulkCodes:chargeConsentText')} + helperText={ + <> + {t('bulkCodes:chargeConsentText')} + {isImport && ( + <> +
+ {t('bulkCodes:invalidEmailWarningText')} + + )} + + } // TODOO translation and pluralization > ); diff --git a/src/features/user/BulkCodes/components/RecipientsUploadForm.tsx b/src/features/user/BulkCodes/components/RecipientsUploadForm.tsx index e60396dc14..86763bfb5f 100644 --- a/src/features/user/BulkCodes/components/RecipientsUploadForm.tsx +++ b/src/features/user/BulkCodes/components/RecipientsUploadForm.tsx @@ -12,6 +12,7 @@ import { } from '../BulkCodesTypes'; import styles from '../BulkCodes.module.scss'; +import { isEmailValid } from '../../../../utils/isEmailValid'; const { Trans, useTranslation } = i18next; @@ -126,19 +127,18 @@ const RecipientsUploadForm = ({ const invalidEmailIndexes: number[] = []; recipients.forEach((recipient, index) => { const { recipient_email } = recipient; - const emailRegex = - /^([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)$/i; - if (!emailRegex.test(recipient_email) && recipient_email.length !== 0) + if (!isEmailValid(recipient_email) && recipient_email.length !== 0) { invalidEmailIndexes.push(index + 1); + } }); if (invalidEmailIndexes.length > 0) { setParseError({ type: 'invalidEmails', message: ready - ? `${t( - 'bulkCodes:errorUploadCSV.invalidEmails' - )} ${invalidEmailIndexes.join(', ')}` + ? t('bulkCodes:errorUploadCSV.invalidEmails', { + rowList: invalidEmailIndexes.join(', '), + }) : '', }); return false; diff --git a/src/features/user/BulkCodes/forms/IssueCodesForm.tsx b/src/features/user/BulkCodes/forms/IssueCodesForm.tsx index 77588dda4b..5d4b259957 100644 --- a/src/features/user/BulkCodes/forms/IssueCodesForm.tsx +++ b/src/features/user/BulkCodes/forms/IssueCodesForm.tsx @@ -241,6 +241,7 @@ const IssueCodesForm = ({}: IssueCodesFormProps): ReactElement | null => { currency={planetCashAccount?.currency} units={getTotalUnits()} unit={project?.unit} + isImport={bulkMethod === 'import'} /> diff --git a/src/utils/isEmailValid.ts b/src/utils/isEmailValid.ts new file mode 100644 index 0000000000..b6aa919031 --- /dev/null +++ b/src/utils/isEmailValid.ts @@ -0,0 +1,22 @@ +const INVALID_DOMAIN_LIST = ['dummy.de', 'example.com', 'company.de']; + +const generateInvalidEmailRegex = (): string => { + const startingPattern = + '^[a-zA-Z0-9_.+-]+@(?:(?:[a-zA-Z0-9-]+.)?[a-zA-Z]+.)?('; + const domainList = INVALID_DOMAIN_LIST.join('|'); + const endingPattern = ')$'; + return `${startingPattern}${domainList}${endingPattern}`; +}; + +const INVALID_DOMAIN_REGEX = new RegExp(generateInvalidEmailRegex(), 'i'); +const EMAIL_FORMAT_REGEX = + /^([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)$/i; + +/** + * Validates provided email address for standard email format, and against excluded/invalid domains + * @param {string} email - Email address to be validated + * @returns boolean + */ +export const isEmailValid = (email: string): boolean => { + return EMAIL_FORMAT_REGEX.test(email) && !INVALID_DOMAIN_REGEX.test(email); +};