diff --git a/public/locales/bg/campaign-application.json b/public/locales/bg/campaign-application.json index edfdb439e..022dcd7d8 100644 --- a/public/locales/bg/campaign-application.json +++ b/public/locales/bg/campaign-application.json @@ -9,7 +9,9 @@ }, "application": { "title": "Информация за кампанията", + "campaignTitle": "Име на кампанията", "beneficiary": "Имена на бенефициента", + "beneficiaryRelationship": "Взаимоотношения с бенефициента", "funds": "Необходима сума в лева", "campaign-end": { "title": "Желана крайна дата на кампанията:", @@ -48,7 +50,8 @@ }, "cta": { "next": "Запазете и продължете", - "back": "Назад" + "back": "Назад", + "submit": "Заявете кампания" }, "remark": { "part-one": "*Допълнителна информация за процеса на кандидатстване и неговите етапи можете да намерите в нашите ", diff --git a/public/locales/en/campaign-application.json b/public/locales/en/campaign-application.json index 206514ddd..c8d3170ba 100644 --- a/public/locales/en/campaign-application.json +++ b/public/locales/en/campaign-application.json @@ -9,7 +9,9 @@ }, "application": { "title": "Campaign Application", + "campaignTitle": "Campaign title", "beneficiary": "Beneficiary names", + "beneficiaryRelationship": "Beneficiary relationship", "funds": "The required amount in leva", "campaign-end": { "title": "Desired campaign end date:", @@ -48,7 +50,8 @@ }, "cta": { "next": "Save and continue", - "back": "Back" + "back": "Back", + "submit": "Submit campaign" }, "remark": { "part-one": "*Additional information about the application process and its stages can be found in our ", diff --git a/src/components/client/campaign-application/CampaignApplicationForm.tsx b/src/components/client/campaign-application/CampaignApplicationForm.tsx index 6667d1409..fdbbcc6bc 100644 --- a/src/components/client/campaign-application/CampaignApplicationForm.tsx +++ b/src/components/client/campaign-application/CampaignApplicationForm.tsx @@ -24,6 +24,20 @@ import { StyledCampaignApplicationStepper, StyledStepConnector, } from './helpers/campaignApplication.styled' +import { useMutation } from '@tanstack/react-query' +import { + CreateCampaignApplicationInput, + CreateCampaignApplicationResponse, +} from 'gql/campaign-applications' +import { AxiosError, AxiosResponse, isAxiosError } from 'axios' +import { ApiErrors, matchValidator } from 'service/apiErrors' +import { useCreateCampaignApplication } from 'service/campaign-application' +import { AlertStore } from 'stores/AlertStore' +import { t } from 'i18next' +import { CampaignTypeCategory } from 'components/common/campaign-types/categories' +import { FormikHelpers } from 'formik' +import { useCampaignTypesList } from 'service/campaignTypes' +import { CampaignTypesResponse } from 'gql/campaign-types' const steps: StepType[] = [ { @@ -46,6 +60,7 @@ type Props = { export default function CampaignApplicationForm({ person }: Props) { const [activeStep, setActiveStep] = useState(Steps.ORGANIZER) + const isLast = activeStep === Steps.CAMPAIGN_DETAILS const initialValues: CampaignApplicationFormData = { organizer: { @@ -57,15 +72,47 @@ export default function CampaignApplicationForm({ person }: Props) { personalInformationProcessingAccepted: false, }, application: { + title: '', beneficiaryNames: '', campaignType: '', funds: 0, campaignEnd: '', }, + details: { + campaignGuarantee: '', + cause: '', + currentStatus: '', + description: '', + documents: [], + links: [], + organizerBeneficiaryRelationship: '-', + otherFinancialSources: '', + }, } - const handleSubmit = () => { - stepsHandler({ activeStep, setActiveStep }) + const { data } = useCampaignTypesList() + const { mutation } = useCreateApplication() + const handleSubmit = async ( + formData: CampaignApplicationFormData, + { setFieldError, resetForm }: FormikHelpers, + ) => { + if (isLast) { + try { + await mutation.mutateAsync(mapCreateInput(formData, data ?? [])) + + resetForm() + } catch (error) { + console.error(error) + if (isAxiosError(error)) { + const { response } = error as AxiosError + response?.data.message.map(({ property, constraints }) => { + setFieldError(property, t(matchValidator(constraints))) + }) + } + } + } else { + stepsHandler({ activeStep, setActiveStep }) + } } const handleBack = useCallback(() => { @@ -92,7 +139,11 @@ export default function CampaignApplicationForm({ person }: Props) { {activeStep < steps.length && steps[activeStep].component} - + @@ -102,3 +153,52 @@ export default function CampaignApplicationForm({ person }: Props) { ) } + +const useCreateApplication = () => { + const mutation = useMutation< + AxiosResponse, + AxiosError, + CreateCampaignApplicationInput + >({ + mutationFn: useCreateCampaignApplication(), + onError: () => AlertStore.show(t('common:alerts.error'), 'error'), + onSuccess: () => AlertStore.show(t('common:alerts.message-sent'), 'success'), + }) + + // const fileUploadMutation = useMutation< + // AxiosResponse, + // AxiosError, + // UploadCampaignFiles + // >({ + // mutationFn: useUploadCampaignFiles(), + // }) + + return { mutation } +} + +function mapCreateInput( + i: CampaignApplicationFormData, + types: CampaignTypesResponse[], +): CreateCampaignApplicationInput { + return { + acceptTermsAndConditions: i.organizer.acceptTermsAndConditions, + personalInformationProcessingAccepted: i.organizer.personalInformationProcessingAccepted, + transparencyTermsAccepted: i.organizer.transparencyTermsAccepted, + + organizerName: i.organizer.name, + organizerEmail: i.organizer.email, + organizerPhone: i.organizer.phone, + + beneficiary: i.application.beneficiaryNames, + + campaignName: i.application.title, + amount: i.application.funds?.toString() ?? '', + goal: i.details.cause, + category: types.find((c) => c.id === i.application.campaignType)?.category, + description: i.details.description, + organizerBeneficiaryRel: i.details.organizerBeneficiaryRelationship ?? '-', + campaignGuarantee: i.details.campaignGuarantee, + history: i.details.currentStatus, + otherFinanceSources: i.details.otherFinancialSources, + } +} diff --git a/src/components/client/campaign-application/CampaignApplicationFormActions.tsx b/src/components/client/campaign-application/CampaignApplicationFormActions.tsx index d77341094..3b2bc6cae 100644 --- a/src/components/client/campaign-application/CampaignApplicationFormActions.tsx +++ b/src/components/client/campaign-application/CampaignApplicationFormActions.tsx @@ -16,11 +16,13 @@ import { type CampaignApplicationFormActionsProps = { activeStep: number onBack?: (event: MouseEvent) => void + isLast: boolean } export default function CampaignApplicationFormActions({ onBack, activeStep, + isLast, }: CampaignApplicationFormActionsProps) { const { t } = useTranslation('campaign-application') @@ -47,7 +49,7 @@ export default function CampaignApplicationFormActions({ } /> diff --git a/src/components/client/campaign-application/helpers/campaignApplication.types.ts b/src/components/client/campaign-application/helpers/campaignApplication.types.ts index aec052ca8..50e3a5990 100644 --- a/src/components/client/campaign-application/helpers/campaignApplication.types.ts +++ b/src/components/client/campaign-application/helpers/campaignApplication.types.ts @@ -21,6 +21,7 @@ export type CampaignApplicationOrganizer = { export type CampaignApplication = { beneficiaryNames: string + title: string campaignType: string funds: number campaignEnd: string @@ -29,6 +30,16 @@ export type CampaignApplication = { export type CampaignApplicationFormData = { organizer: CampaignApplicationOrganizer application: CampaignApplication + details: { + organizerBeneficiaryRelationship: string + campaignGuarantee: string | undefined + otherFinancialSources: string | undefined + description: string + currentStatus: string + cause: string + links: string[] + documents: string[] + } } export type CampaignApplicationFormDataSteps = { diff --git a/src/components/client/campaign-application/steps/CampaignApplication.tsx b/src/components/client/campaign-application/steps/CampaignApplication.tsx index 773352f92..b39b482fc 100644 --- a/src/components/client/campaign-application/steps/CampaignApplication.tsx +++ b/src/components/client/campaign-application/steps/CampaignApplication.tsx @@ -23,12 +23,26 @@ export default function CampaignApplication() { - + + + + + + + { url: '/campaign-application/create', method: 'POST' }, + }, } diff --git a/src/service/campaign-application.ts b/src/service/campaign-application.ts new file mode 100644 index 000000000..d0ac528d9 --- /dev/null +++ b/src/service/campaign-application.ts @@ -0,0 +1,19 @@ +import { AxiosResponse } from 'axios' +import { useSession } from 'next-auth/react' + +import { apiClient } from 'service/apiClient' +import { endpoints } from 'service/apiEndpoints' +import { authConfig } from 'service/restRequests' +import { + CreateCampaignApplicationInput, + CreateCampaignApplicationResponse, +} from 'gql/campaign-applications' + +export const useCreateCampaignApplication = () => { + const { data: session } = useSession() + return async (data: CreateCampaignApplicationInput) => + await apiClient.post< + CreateCampaignApplicationInput, + AxiosResponse + >(endpoints.campaignApplication.create.url, data, authConfig(session?.accessToken)) +}