From dc567c705e776217f05348ca23a58f2e535d5918 Mon Sep 17 00:00:00 2001 From: Vitaliy Kormylo <65959529+VKormylo@users.noreply.github.com> Date: Tue, 3 Dec 2024 22:24:19 +0200 Subject: [PATCH] Fixed invalid additionalInfo validation in enroll offer modal (#2918) * Fixed invalid additionalInfo validation in enroll offer modal * Fixed code coverage * Refactored code for better clarity * Fixed some ternary operators * Fixed incorrect empty addtionalInfo sending in request --- src/components/app-text-area/AppTextArea.tsx | 9 ++- .../translations/en/offer-details-page.json | 5 +- .../translations/uk/offer-details-page.json | 5 +- .../enroll-offer/EnrollOffer.styles.ts | 4 + .../enroll-offer/EnrollOffer.tsx | 74 ++++++++++++++----- .../enroll-offer/EnrollOffer.spec.jsx | 19 +++++ 6 files changed, 93 insertions(+), 23 deletions(-) diff --git a/src/components/app-text-area/AppTextArea.tsx b/src/components/app-text-area/AppTextArea.tsx index 927bae821..4f0e6fa1c 100644 --- a/src/components/app-text-area/AppTextArea.tsx +++ b/src/components/app-text-area/AppTextArea.tsx @@ -13,6 +13,7 @@ import { styles } from '~/components/app-text-area/AppTextArea.styles' interface AppTextAreaProps extends Omit { maxLength?: number + minLength?: number errorMsg?: string value?: string textFieldStyles?: SxProps @@ -24,6 +25,7 @@ const AppTextArea: FC = ({ minRows = 4, maxRows = 4, maxLength, + minLength, title, value, sx, @@ -35,6 +37,11 @@ const AppTextArea: FC = ({ const textLengthStyle = isRightAligned ? styles.textLengthRight : styles.textLength + const isLengthTooShort = + minLength && value?.length < minLength && value?.length !== 0 + const isLengthTooLong = value?.length === maxLength + const isLengthValid = + isLengthTooShort || isLengthTooLong ? 'error' : 'primary.300' return ( @@ -50,7 +57,7 @@ const AppTextArea: FC = ({ /> {maxLength && ( diff --git a/src/constants/translations/en/offer-details-page.json b/src/constants/translations/en/offer-details-page.json index 18c796f41..b9a09452d 100644 --- a/src/constants/translations/en/offer-details-page.json +++ b/src/constants/translations/en/offer-details-page.json @@ -35,5 +35,8 @@ "offerBanner": { "saveOfferButton": "Save Offer" }, - "closeOffer": "Are you sure that you want to close this offer? You can't reopen this offer." + "closeOffer": "Are you sure that you want to close this offer? You can't reopen this offer.", + "errors": { + "additionalInfo": "Additional information cannot be shorter than 30 symbols" + } } diff --git a/src/constants/translations/uk/offer-details-page.json b/src/constants/translations/uk/offer-details-page.json index 558415efb..ec4fc1390 100644 --- a/src/constants/translations/uk/offer-details-page.json +++ b/src/constants/translations/uk/offer-details-page.json @@ -35,5 +35,8 @@ "offerBanner": { "saveOfferButton": "Зберегти пропозицію" }, - "closeOffer": "Ви впевнені, що бажаєте закрити цю пропозицію? Ви не можете повторно відкрити цю пропозицію." + "closeOffer": "Ви впевнені, що бажаєте закрити цю пропозицію? Ви не можете повторно відкрити цю пропозицію.", + "errors": { + "additionalInfo": "Додаткова інформація не може бути коротшою за 30 символів" + } } diff --git a/src/containers/offer-details/enroll-offer/EnrollOffer.styles.ts b/src/containers/offer-details/enroll-offer/EnrollOffer.styles.ts index b6bd352d0..af402419c 100644 --- a/src/containers/offer-details/enroll-offer/EnrollOffer.styles.ts +++ b/src/containers/offer-details/enroll-offer/EnrollOffer.styles.ts @@ -36,5 +36,9 @@ export const styles = { p: '12px 24px', width: { xs: '100%', sm: 'fit-content' }, minWidth: '280px' + }, + errorText: { + ml: '12.5px', + mt: '3px' } } diff --git a/src/containers/offer-details/enroll-offer/EnrollOffer.tsx b/src/containers/offer-details/enroll-offer/EnrollOffer.tsx index 3b2138b1e..21a2d23fd 100644 --- a/src/containers/offer-details/enroll-offer/EnrollOffer.tsx +++ b/src/containers/offer-details/enroll-offer/EnrollOffer.tsx @@ -26,10 +26,13 @@ import { ErrorResponse, Offer, EnrollOfferForm, - ButtonTypeEnum + ButtonTypeEnum, + TypographyVariantEnum } from '~/types' import { openAlert } from '~/redux/features/snackbarSlice' import { getErrorKey } from '~/utils/get-error-key' +import { textField } from '~/utils/validations/common' +import { Typography } from '@mui/material' interface EnrollOfferProps { offer: Offer @@ -80,16 +83,35 @@ const EnrollOffer: FC = ({ offer, enrollOffer }) => { onResponseError: handleResponseError }) - const { data, handleInputChange, handleNonInputValueChange, handleSubmit } = - useForm({ - initialValues: { - proficiencyLevel: offer.proficiencyLevel[0], - price: offer.price, - additionalInfo: '', - title: offer.title - }, - onSubmit: fetchData - }) + const validateAdditionalInfo = (additionalInfoValue: string) => { + if (additionalInfoValue.length === 0) { + delete data.additionalInfo + } + if (additionalInfoValue.length < 30 && additionalInfoValue.length !== 0) { + return textField(30, 1000) + } + } + + const validations = { + additionalInfo: validateAdditionalInfo + } + + const { + data, + errors, + handleInputChange, + handleNonInputValueChange, + handleSubmit + } = useForm({ + initialValues: { + proficiencyLevel: offer.proficiencyLevel[0], + price: offer.price, + additionalInfo: '', + title: offer.title + }, + validations: validations, + onSubmit: fetchData + }) const levelOptions = offer.proficiencyLevel.map((level) => ({ title: level, @@ -135,15 +157,27 @@ const EnrollOffer: FC = ({ offer, enrollOffer }) => { onChange={handleFieldChange('price')} title={t('offerDetailsPage.enrollOffer.labels.preferredPrice')} /> - + + + {errors.additionalInfo && ( + + {t('offerDetailsPage.errors.additionalInfo')} + + )} + { expect(levelSelect.value).toBe(newLevel) }) + it('should display error message', () => { + const newAdditionalInfo = 'Some text' + const additionalInfoInput = screen.getByLabelText( + 'offerDetailsPage.enrollOffer.labels.info' + ) + fireEvent.change(additionalInfoInput, { + target: { value: newAdditionalInfo } + }) + expect(additionalInfoInput.value).toBe(newAdditionalInfo) + + const button = screen.getByText('button.createCooperation') + waitFor(() => fireEvent.click(button)) + + const errorMessage = screen.getByText( + 'offerDetailsPage.errors.additionalInfo' + ) + expect(errorMessage).toBeInTheDocument() + }) + it('should send form', () => { const button = screen.getByText('button.createCooperation')