From 2f208bfb0b4903558a310183598907448a5e7952 Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:30:12 -0500 Subject: [PATCH 1/6] Standardizes AddDonation form errors behavior --- .../AddMenu/Items/AddDonation/AddDonation.tsx | 54 ++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx b/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx index 2cf4cc9592..f79581d12d 100644 --- a/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx @@ -13,6 +13,7 @@ import { useMediaQuery, } from '@mui/material'; import { FastField, Field, FieldProps, Form, Formik } from 'formik'; +import i18n from 'i18next'; import { DateTime } from 'luxon'; import { useSnackbar } from 'notistack'; import { useTranslation } from 'react-i18next'; @@ -41,38 +42,40 @@ interface AddDonationProps { const donationSchema = yup.object({ amount: yup .number() - .typeError('Amount must be a valid number') - .required() + .typeError(i18n.t('Amount must be a valid number')) + .required(i18n.t('Amount is required')) .test( 'Is amount in valid currency format?', - 'Amount must be in valid currency format', + i18n.t('Amount must be in valid currency format'), (amount) => /\$?[0-9][0-9.,]*/.test(amount as unknown as string), ) .test( 'Is positive?', - 'Must use a positive number for amount', + i18n.t('Must use a positive number for amount'), (value) => parseFloat(value as unknown as string) > 0, ), appealAmount: yup .number() - .typeError('Appeal amount must be a valid number') + .typeError(i18n.t('Appeal amount must be a valid number')) .nullable() .test( 'Is appeal amount in valid currency format?', - 'Appeal amount must be in valid currency format', + i18n.t('Appeal amount must be in valid currency format'), (amount) => !amount || /\$?[0-9][0-9.,]*/.test(amount as unknown as string), ) .test( 'Is positive?', - 'Must use a positive number for appeal amount', + i18n.t('Must use a positive number for appeal amount'), (value) => !value || parseFloat(value as unknown as string) > 0, ), appealId: yup.string().nullable(), - currency: yup.string().required(), - designationAccountId: yup.string().required(), - donationDate: requiredDateTime(), - donorAccountId: yup.string().required(), + currency: yup.string().required(i18n.t('Currency is required')), + designationAccountId: yup + .string() + .required(i18n.t('Designation account is required')), + donationDate: requiredDateTime(i18n.t('Date is required')), + donorAccountId: yup.string().required(i18n.t('Partner Account is required')), memo: yup.string().nullable(), motivation: yup.string().nullable(), paymentMethod: yup.string().nullable(), @@ -208,6 +211,7 @@ export const AddDonation = ({ {...field} size="small" variant="outlined" + onBlur={handleBlur('amount')} fullWidth type="text" inputProps={{ @@ -244,11 +248,13 @@ export const AddDonation = ({ id="currency-select" disableClearable value={currency} + onBlur={handleBlur('currency')} onChange={(_, currencyCode) => { setFieldValue('currency', currencyCode); }} textFieldProps={{ - error: !!errors.currency, + error: !!errors.currency && touched.currency, + helperText: touched.currency && errors.currency, }} size="small" /> @@ -285,6 +291,14 @@ export const AddDonation = ({ onChange={(date) => setFieldValue('donationDate', date) } + onBlur={handleBlur('donationDate')} + error={ + !!(errors.donationDate && touched.donationDate) + } + helperText={ + touched.donationDate && + (errors.donationDate as string) + } /> )} @@ -348,6 +362,13 @@ export const AddDonation = ({ setFieldValue('donorAccountId', donorAccountId) } onBlur={handleBlur('donorAccountId')} + textFieldProps={{ + error: + !!errors.donorAccountId && + touched.donorAccountId, + helperText: + touched.donorAccountId && errors.donorAccountId, + }} value={field.value} autocompleteId="partner-account-input" labelId="partner-account-label" @@ -383,6 +404,7 @@ export const AddDonation = ({ loading={designationAccountsLoading} autoSelect autoHighlight + onBlur={handleBlur('designationAccountId')} options={ designationAccounts?.map(({ id }) => id) ?? [] } @@ -399,6 +421,14 @@ export const AddDonation = ({ {...params} size="small" variant="outlined" + error={ + !!errors.designationAccountId && + touched.designationAccountId + } + helperText={ + touched.designationAccountId && + errors.designationAccountId + } InputProps={{ ...params.InputProps, 'aria-labelledby': From 819e6b135824484b950d42a58f024aa8e142b1dd Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:31:36 -0500 Subject: [PATCH 2/6] Change CustomDateField, CurrencyAutocomplete, DonorAccountAutocomplete to accept onBlur and/or custom TextFieldProps props --- .../CurrencyAutocomplete/CurrencyAutocomplete.tsx | 3 +++ .../DonorAccountAutocomplete/DonorAccountAutocomplete.tsx | 4 ++++ src/components/common/DateTimePickers/CustomDateField.tsx | 4 +++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/common/Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx b/src/components/common/Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx index d828321948..d6179039bb 100644 --- a/src/components/common/Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx +++ b/src/components/common/Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx @@ -15,11 +15,13 @@ interface CurrencyAutocompleteProps extends Partial> { textFieldProps?: Partial; format?: PledgeCurrencyOptionFormatEnum; + onBlur?: FocusEventHandler; } export const CurrencyAutocomplete = ({ textFieldProps, format = PledgeCurrencyOptionFormatEnum.Long, + onBlur, ...props }: CurrencyAutocompleteProps) => { const constants = useApiConstants(); @@ -49,6 +51,7 @@ export const CurrencyAutocomplete = ({ return selectedCurrency.codeSymbolString ?? ''; }} renderInput={(params) => } + onBlur={onBlur} /> ); }; diff --git a/src/components/common/Autocomplete/DonorAccountAutocomplete/DonorAccountAutocomplete.tsx b/src/components/common/Autocomplete/DonorAccountAutocomplete/DonorAccountAutocomplete.tsx index fa89a2de0c..4bf3de3011 100644 --- a/src/components/common/Autocomplete/DonorAccountAutocomplete/DonorAccountAutocomplete.tsx +++ b/src/components/common/Autocomplete/DonorAccountAutocomplete/DonorAccountAutocomplete.tsx @@ -4,6 +4,7 @@ import { BaseTextFieldProps, CircularProgress, TextField, + TextFieldProps, } from '@mui/material'; import { map, unionBy } from 'lodash'; import { useDebouncedCallback } from 'src/hooks/useDebounce'; @@ -19,6 +20,7 @@ export interface DonorAccountAutocompleteProps { labelId?: string; label?: string; size?: BaseTextFieldProps['size']; + textFieldProps?: Partial; } export const DonorAccountAutocomplete: React.FC< @@ -33,6 +35,7 @@ export const DonorAccountAutocomplete: React.FC< labelId, label, size, + textFieldProps, }) => { const [searchForDonorAccounts, { loading, data: donorAccountData }] = useGetDonorAccountsLazyQuery(); @@ -66,6 +69,7 @@ export const DonorAccountAutocomplete: React.FC< renderInput={(params) => ( = (props) => { const isDesktop = useMediaQuery(DEFAULT_DESKTOP_MODE_MEDIA_QUERY, { defaultMatches: true, }); - const { label, value, invalidDate, onChange, ...textFieldProps } = props; + const { label, value, invalidDate, onBlur, onChange, ...textFieldProps } = + props; // If value is not valid render desktop input as it can render invalid value if (isDesktop || invalidDate) { @@ -26,6 +27,7 @@ export const CustomDateField: React.FC = (props) => { slotProps={{ textField: { fullWidth: true, + onBlur: onBlur, InputProps: { endAdornment: ( From b4544f81ff035e3c82db9b30052d3bc8c3ed5565 Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:33:38 -0500 Subject: [PATCH 3/6] fixup: Import FocusEventHandler in CurrencyAutocomplete --- .../Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/common/Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx b/src/components/common/Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx index d6179039bb..d9f0b3793e 100644 --- a/src/components/common/Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx +++ b/src/components/common/Autocomplete/CurrencyAutocomplete/CurrencyAutocomplete.tsx @@ -1,3 +1,4 @@ +import { FocusEventHandler } from 'react'; import { Autocomplete, AutocompleteProps, From 84967f17a6ab62e7663fedb648ad9910ce7a1192 Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Wed, 16 Jul 2025 10:56:26 -0500 Subject: [PATCH 4/6] Change onBlur handler to adhere to Formik expectations, for fields where onBlur is required --- .../AddMenu/Items/AddDonation/AddDonation.tsx | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx b/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx index f79581d12d..ba7f532282 100644 --- a/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx @@ -179,8 +179,8 @@ export const AddDonation = ({ > {({ values: { appealId, currency }, - handleBlur, setFieldValue, + setFieldTouched, isSubmitting, isValid, errors, @@ -211,7 +211,11 @@ export const AddDonation = ({ {...field} size="small" variant="outlined" - onBlur={handleBlur('amount')} + onChange={(e) => { + setFieldValue('amount', e.target.value); + setFieldTouched('amount', true, false); + }} + onBlur={() => setFieldTouched('amount', true)} fullWidth type="text" inputProps={{ @@ -248,10 +252,11 @@ export const AddDonation = ({ id="currency-select" disableClearable value={currency} - onBlur={handleBlur('currency')} onChange={(_, currencyCode) => { setFieldValue('currency', currencyCode); + setFieldTouched('currency', true, false); }} + onBlur={() => setFieldTouched('currency', true)} textFieldProps={{ error: !!errors.currency && touched.currency, helperText: touched.currency && errors.currency, @@ -288,10 +293,11 @@ export const AddDonation = ({ 'aria-labelledby': 'date-label', }} {...field} - onChange={(date) => - setFieldValue('donationDate', date) - } - onBlur={handleBlur('donationDate')} + onChange={(date) => { + setFieldValue('donationDate', date); + setFieldTouched('donationDate', true, false); + }} + onBlur={() => setFieldTouched('donationDate', true)} error={ !!(errors.donationDate && touched.donationDate) } @@ -358,10 +364,13 @@ export const AddDonation = ({ - setFieldValue('donorAccountId', donorAccountId) + onChange={(donorAccountId) => { + setFieldValue('donorAccountId', donorAccountId); + setFieldTouched('donorAccountId', true, false); + }} + onBlur={() => + setFieldTouched('donorAccountId', true) } - onBlur={handleBlur('donorAccountId')} textFieldProps={{ error: !!errors.donorAccountId && @@ -404,7 +413,20 @@ export const AddDonation = ({ loading={designationAccountsLoading} autoSelect autoHighlight - onBlur={handleBlur('designationAccountId')} + onChange={(_, designationAccountId) => { + setFieldValue( + 'designationAccountId', + designationAccountId, + ); + setFieldTouched( + 'designationAccountId', + true, + false, + ); + }} + onBlur={() => + setFieldTouched('designationAccountId', true) + } options={ designationAccounts?.map(({ id }) => id) ?? [] } @@ -448,12 +470,6 @@ export const AddDonation = ({ /> )} value={field.value} - onChange={(_, designationAccountId) => - setFieldValue( - 'designationAccountId', - designationAccountId, - ) - } /> )} From 3a34c77e76e0c7bcc6b3b0af6dd8ca599b81f750 Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:33:52 -0500 Subject: [PATCH 5/6] Removes onBlur from CustomDateField, it doesn't work as intended and is therefore redundant --- .../Items/AddMenu/Items/AddDonation/AddDonation.tsx | 11 ++--------- .../common/DateTimePickers/CustomDateField.tsx | 4 +--- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx b/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx index ba7f532282..99f51f7041 100644 --- a/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx @@ -295,16 +295,9 @@ export const AddDonation = ({ {...field} onChange={(date) => { setFieldValue('donationDate', date); - setFieldTouched('donationDate', true, false); }} - onBlur={() => setFieldTouched('donationDate', true)} - error={ - !!(errors.donationDate && touched.donationDate) - } - helperText={ - touched.donationDate && - (errors.donationDate as string) - } + error={!!errors.donationDate} + helperText={errors.donationDate as string} /> )} diff --git a/src/components/common/DateTimePickers/CustomDateField.tsx b/src/components/common/DateTimePickers/CustomDateField.tsx index 3dc4f415ee..2bc625147c 100644 --- a/src/components/common/DateTimePickers/CustomDateField.tsx +++ b/src/components/common/DateTimePickers/CustomDateField.tsx @@ -11,8 +11,7 @@ export const CustomDateField: React.FC = (props) => { const isDesktop = useMediaQuery(DEFAULT_DESKTOP_MODE_MEDIA_QUERY, { defaultMatches: true, }); - const { label, value, invalidDate, onBlur, onChange, ...textFieldProps } = - props; + const { label, value, invalidDate, onChange, ...textFieldProps } = props; // If value is not valid render desktop input as it can render invalid value if (isDesktop || invalidDate) { @@ -27,7 +26,6 @@ export const CustomDateField: React.FC = (props) => { slotProps={{ textField: { fullWidth: true, - onBlur: onBlur, InputProps: { endAdornment: ( From a3dad43fb5bd4964e946ab4e267b4ff72b9b8e59 Mon Sep 17 00:00:00 2001 From: zachery with an e <45150570+zweatshirt@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:34:56 -0500 Subject: [PATCH 6/6] Changes i18n import statement --- .../TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx b/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx index 99f51f7041..2757a99cb1 100644 --- a/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/AddMenu/Items/AddDonation/AddDonation.tsx @@ -13,7 +13,6 @@ import { useMediaQuery, } from '@mui/material'; import { FastField, Field, FieldProps, Form, Formik } from 'formik'; -import i18n from 'i18next'; import { DateTime } from 'luxon'; import { useSnackbar } from 'notistack'; import { useTranslation } from 'react-i18next'; @@ -28,6 +27,7 @@ import { SubmitButton, } from 'src/components/common/Modal/ActionButtons/ActionButtons'; import { requiredDateTime } from 'src/lib/formikHelpers'; +import i18n from 'src/lib/i18n'; import { useAddDonationMutation, useGetDonationModalQuery,