diff --git a/public/locales/bg/campaign-application.json b/public/locales/bg/campaign-application.json index 9d8178c6e..c18e4a943 100644 --- a/public/locales/bg/campaign-application.json +++ b/public/locales/bg/campaign-application.json @@ -41,7 +41,8 @@ }, "photos": "Снимков/видео материал", "documents": "Документи", - "disclaimer": "Приемат се до 10 документа. Всеки от документите може да бъде до 30МБ" + "disclaimer": "Приемат се до 10 документа. Всеки от документите може да бъде до 30МБ", + "documents-hint": "Моля, приложете поне един документ в подкрепа на нуждата ви, като например: медицинска документация, оферти за ремонт, обучение или лечение, писма или становища от институции, детайлен бюджет, оферти за необходими артикули или услуги, както и други документи, свързани с каузата ви." }, "admin": { "title": "Администраторска редакция", diff --git a/public/locales/en/campaign-application.json b/public/locales/en/campaign-application.json index 175d24561..45f9abbcd 100644 --- a/public/locales/en/campaign-application.json +++ b/public/locales/en/campaign-application.json @@ -41,7 +41,8 @@ }, "photos": "Photo/Video material", "documents": "Documents", - "disclaimer": "Up to 10 documents accepted. Each of the documents can be up to 30MB is size" + "disclaimer": "Up to 10 documents accepted. Each of the documents can be up to 30MB is size", + "documents-hint": "Please attach at least one document to stake your claim like: medical documents, offers for repair learning or healthcare, letters from institutions, detailed budget, offers for required items or services, as well as any other documents to do with your cause." }, "admin": { "title": "Admin edit", diff --git a/src/components/client/campaign-application/CampaignApplicationForm.tsx b/src/components/client/campaign-application/CampaignApplicationForm.tsx index 42e7e6c54..21b3bf32e 100644 --- a/src/components/client/campaign-application/CampaignApplicationForm.tsx +++ b/src/components/client/campaign-application/CampaignApplicationForm.tsx @@ -84,7 +84,7 @@ export default function CampaignApplicationForm({ const handleSubmit = async ( formData: CampaignApplicationFormData, - { resetForm }: FormikHelpers, + { resetForm, setTouched }: FormikHelpers, ) => { if (activeStep === Steps.CREATED_DETAILS && camApp?.id != null) { router.push(routes.campaigns.applicationEdit(camApp?.id)) // go to the edit page @@ -100,6 +100,26 @@ export default function CampaignApplicationForm({ } } else { setActiveStep((prevActiveStep) => prevActiveStep + 1) + + // reset the touched state b/c every step gets "submitted" in terms of formik and that in turn marks all values as touched + // the effect is that every step after the first immediately marks all required fields as errored even before the user has touched them + setTouched({ + applicationDetails: { + description: false, + currentStatus: false, + cause: false, + documents: false, + }, + applicationBasic: { + beneficiaryNames: false, + title: false, + campaignType: false, + funds: false, + campaignEnd: false, + campaignEndDate: false, + organizerBeneficiaryRelationship: false, + }, + }) } } diff --git a/src/components/client/campaign-application/helpers/campaignApplication.types.ts b/src/components/client/campaign-application/helpers/campaignApplication.types.ts index cba46e6d5..7a4a61082 100644 --- a/src/components/client/campaign-application/helpers/campaignApplication.types.ts +++ b/src/components/client/campaign-application/helpers/campaignApplication.types.ts @@ -26,13 +26,14 @@ export type CampaignApplicationBasic = { funds: number campaignEnd: string campaignEndDate?: string + organizerBeneficiaryRelationship?: string } export type CampaignApplicationDetails = { cause: string - organizerBeneficiaryRelationship?: string description?: string currentStatus?: string + documents?: File[] } // keep in sync with api repo/podkrepi.dbml -> Enum CampaignApplicationState diff --git a/src/components/client/campaign-application/helpers/validation-schema.ts b/src/components/client/campaign-application/helpers/validation-schema.ts index 58f1d7598..e1a1e256c 100644 --- a/src/components/client/campaign-application/helpers/validation-schema.ts +++ b/src/components/client/campaign-application/helpers/validation-schema.ts @@ -31,6 +31,7 @@ const basicSchema: yup.SchemaOf = yup.object().shape({ funds: yup.number().required(), title: yup.string().required(), campaignEndDate: yup.string().optional(), + organizerBeneficiaryRelationship: yup.string().required(), }) const detailsSchema: yup.SchemaOf = yup.object().shape({ @@ -38,9 +39,8 @@ const detailsSchema: yup.SchemaOf = yup.object().sha campaignGuarantee: yup.string().optional(), currentStatus: yup.string().optional(), description: yup.string().optional(), - documents: yup.array().optional(), + documents: yup.array().min(1, 'documents-hint').required(), links: yup.array().optional(), - organizerBeneficiaryRelationship: yup.string().optional(), otherFinancialSources: yup.string().optional(), }) diff --git a/src/components/client/campaign-application/steps/CampaignApplicationBasic.tsx b/src/components/client/campaign-application/steps/CampaignApplicationBasic.tsx index 807e746b2..1c8ca9799 100644 --- a/src/components/client/campaign-application/steps/CampaignApplicationBasic.tsx +++ b/src/components/client/campaign-application/steps/CampaignApplicationBasic.tsx @@ -47,7 +47,7 @@ export default function CampaignApplicationBasic() { diff --git a/src/components/client/campaign-application/steps/CampaignApplicationDetails.tsx b/src/components/client/campaign-application/steps/CampaignApplicationDetails.tsx index 30d8dffa6..b5454057c 100644 --- a/src/components/client/campaign-application/steps/CampaignApplicationDetails.tsx +++ b/src/components/client/campaign-application/steps/CampaignApplicationDetails.tsx @@ -1,4 +1,4 @@ -import { Grid, Typography } from '@mui/material' +import { FormControl, FormHelperText, Grid, Typography } from '@mui/material' import { useTranslation } from 'next-i18next' import FormTextField from 'components/common/form/FormTextField' @@ -6,7 +6,9 @@ import { StyledStepHeading } from '../helpers/campaignApplication.styled' import FileList from 'components/common/file-upload/FileList' import FileUpload from 'components/common/file-upload/FileUpload' -import { Dispatch, SetStateAction } from 'react' +import { Dispatch, SetStateAction, useEffect } from 'react' +import { useFormikContext } from 'formik' +import { CampaignApplicationFormData } from '../helpers/campaignApplication.types' export type Props = { files: File[] @@ -15,6 +17,11 @@ export type Props = { export default function CampaignApplicationDetails({ files, setFiles }: Props) { const { t } = useTranslation('campaign-application') + const { setFieldValue, errors, touched } = useFormikContext() + + useEffect(() => { + setFieldValue('applicationDetails.documents', files, false) + }, [files]) return ( @@ -51,13 +58,20 @@ export default function CampaignApplicationDetails({ files, setFiles }: Props) { /> - { - setFiles((prevFiles) => [...prevFiles, ...newFiles]) - }} - accept="text/plain,application/json,application/pdf,image/png,image/jpeg,application/xml,text/xml,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - /> + + {t('steps.details.documents-hint')} + { + setFiles((prevFiles) => [...prevFiles, ...newFiles]) + setFieldValue('applicationDetails.documents', newFiles) + }} + accept="text/plain,application/json,application/pdf,image/png,image/jpeg,application/xml,text/xml,application/msword,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + /> + {touched.applicationDetails?.documents && errors.applicationDetails?.documents && ( + {t('steps.details.documents-hint')} + )} + {t('steps.details.disclaimer')} () return ( @@ -68,6 +71,12 @@ export default function CampaignApplicationOrganizer({ isAdmin }: Props) { name="organizer.personalInformationProcessingAccepted" disabled={isAdmin} /> + {touched.organizer?.personalInformationProcessingAccepted && + errors.organizer?.personalInformationProcessingAccepted && ( + + {t(errors.organizer.personalInformationProcessingAccepted)} + + )} diff --git a/src/components/common/form/AcceptPrivacyPolicyField.tsx b/src/components/common/form/AcceptPrivacyPolicyField.tsx index 99a4629b3..314118470 100644 --- a/src/components/common/form/AcceptPrivacyPolicyField.tsx +++ b/src/components/common/form/AcceptPrivacyPolicyField.tsx @@ -6,7 +6,7 @@ import { routes } from 'common/routes' import ExternalLink from 'components/common/ExternalLink' import CheckboxField, { CheckboxFieldProps } from 'components/common/form/CheckboxField' -export type AcceptGDPRFieldProps = { +export type AcceptGDPRFieldProps = Omit & { name: string showFieldError?: boolean disabled?: boolean diff --git a/src/service/campaign-application.ts b/src/service/campaign-application.ts index c909b5536..4a3421d09 100644 --- a/src/service/campaign-application.ts +++ b/src/service/campaign-application.ts @@ -261,12 +261,12 @@ export function mapExistingOrNew( funds: isNaN(parseInt(existing?.amount ?? '')) ? 0 : parseInt(existing?.amount ?? '0'), campaignEnd: existing?.campaignEnd ?? CampaignEndTypes.FUNDS, campaignEndDate: existing?.campaignEndDate, + organizerBeneficiaryRelationship: existing?.organizerBeneficiaryRel ?? '', }, applicationDetails: { cause: existing?.goal ?? '', currentStatus: existing?.history ?? '', description: existing?.description ?? '', - organizerBeneficiaryRelationship: existing?.organizerBeneficiaryRel ?? '', }, admin: { archived: existing?.archived ?? false, @@ -292,7 +292,7 @@ export function mapCreateOrEditInput(i: CampaignApplicationFormData): CampaignAp amount: i.applicationBasic.funds?.toString() ?? '', goal: i.applicationDetails.cause, description: i.applicationDetails.description, - organizerBeneficiaryRel: i.applicationDetails.organizerBeneficiaryRelationship ?? '-', + organizerBeneficiaryRel: i.applicationBasic.organizerBeneficiaryRelationship ?? '-', history: i.applicationDetails.currentStatus, campaignEnd: i.applicationBasic.campaignEnd, campaignEndDate: i.applicationBasic.campaignEndDate,