Skip to content

Commit

Permalink
Create campaign application (#1893)
Browse files Browse the repository at this point in the history
* feat: create application

#1842
- add missing field title and relationship
- send post

* fix: add beneficiary relationship
  • Loading branch information
gparlakov committed Aug 26, 2024
1 parent 5471218 commit 64d8c0d
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 8 deletions.
5 changes: 4 additions & 1 deletion public/locales/bg/campaign-application.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
},
"application": {
"title": "Информация за кампанията",
"campaignTitle": "Име на кампанията",
"beneficiary": "Имена на бенефициента",
"beneficiaryRelationship": "Взаимоотношения с бенефициента",
"funds": "Необходима сума в лева",
"campaign-end": {
"title": "Желана крайна дата на кампанията:",
Expand Down Expand Up @@ -48,7 +50,8 @@
},
"cta": {
"next": "Запазете и продължете",
"back": "Назад"
"back": "Назад",
"submit": "Заявете кампания"
},
"remark": {
"part-one": "*Допълнителна информация за процеса на кандидатстване и неговите етапи можете да намерите в нашите ",
Expand Down
5 changes: 4 additions & 1 deletion public/locales/en/campaign-application.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:",
Expand Down Expand Up @@ -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 ",
Expand Down
106 changes: 103 additions & 3 deletions src/components/client/campaign-application/CampaignApplicationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
{
Expand All @@ -46,6 +60,7 @@ type Props = {

export default function CampaignApplicationForm({ person }: Props) {
const [activeStep, setActiveStep] = useState<Steps>(Steps.ORGANIZER)
const isLast = activeStep === Steps.CAMPAIGN_DETAILS

const initialValues: CampaignApplicationFormData = {
organizer: {
Expand All @@ -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<CampaignApplicationFormData>,
) => {
if (isLast) {
try {
await mutation.mutateAsync(mapCreateInput(formData, data ?? []))

resetForm()
} catch (error) {
console.error(error)
if (isAxiosError(error)) {
const { response } = error as AxiosError<ApiErrors>
response?.data.message.map(({ property, constraints }) => {
setFieldError(property, t(matchValidator(constraints)))
})
}
}
} else {
stepsHandler({ activeStep, setActiveStep })
}
}

const handleBack = useCallback(() => {
Expand All @@ -92,7 +139,11 @@ export default function CampaignApplicationForm({ person }: Props) {
{activeStep < steps.length && steps[activeStep].component}
</Grid>
<Grid container item alignContent="center">
<CampaignApplicationFormActions activeStep={activeStep} onBack={handleBack} />
<CampaignApplicationFormActions
activeStep={activeStep}
onBack={handleBack}
isLast={isLast}
/>
</Grid>
</Grid>
</GenericForm>
Expand All @@ -102,3 +153,52 @@ export default function CampaignApplicationForm({ person }: Props) {
</>
)
}

const useCreateApplication = () => {
const mutation = useMutation<
AxiosResponse<CreateCampaignApplicationResponse>,
AxiosError<ApiErrors>,
CreateCampaignApplicationInput
>({
mutationFn: useCreateCampaignApplication(),
onError: () => AlertStore.show(t('common:alerts.error'), 'error'),
onSuccess: () => AlertStore.show(t('common:alerts.message-sent'), 'success'),
})

// const fileUploadMutation = useMutation<
// AxiosResponse<CampaignUploadImage[]>,
// AxiosError<ApiErrors>,
// 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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -47,7 +49,7 @@ export default function CampaignApplicationFormActions({
<Grid item xs={12} md={6} flexWrap="nowrap">
<ActionSubmitButton
fullWidth
label={t('cta.next')}
label={t(isLast ? 'cta.submit' : 'cta.next')}
endIcon={<ArrowForwardIosIcon fontSize="small" />}
/>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type CampaignApplicationOrganizer = {

export type CampaignApplication = {
beneficiaryNames: string
title: string
campaignType: string
funds: number
campaignEnd: string
Expand All @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,26 @@ export default function CampaignApplication() {
<StyledFormTextField
label={t('steps.application.beneficiary')}
type="text"
name="application.beneficiary"
name="application.beneficiaryNames"
autoComplete="name"
/>
</Grid>
<Grid item xs={12}>
<CampaignTypeSelect />
<StyledFormTextField
label={t('steps.application.beneficiaryRelationship')}
type="text"
name="details.beneficiaryNames"
/>
</Grid>
<Grid item xs={12}>
<StyledFormTextField
label={t('steps.application.campaignTitle')}
type="text"
name="application.title"
/>
</Grid>
<Grid item xs={12}>
<CampaignTypeSelect name="application.campaignType" />
</Grid>
<Grid item xs={12}>
<StyledFormTextField
Expand Down
59 changes: 59 additions & 0 deletions src/gql/campaign-applications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { CampaignTypeCategory } from 'components/common/campaign-types/categories'

export class CreateCampaignApplicationInput {
/**
* What would the campaign be called. ('Help Vesko' or 'Castrate Plovdiv Cats')
*/
campaignName: string

/** user needs to agree to this as a prerequisite to creating a campaign application */
acceptTermsAndConditions: boolean

/** user needs to agree to this as a prerequisite to creating a campaign application */
transparencyTermsAccepted: boolean

/** user needs to agree to this as a prerequisite to creating a campaign application */
personalInformationProcessingAccepted: boolean

/** Who is organizing this campaign */
organizerName: string

/** Contact Email to use for the Campaign Application process i.e. if more documents or other info are requested */
organizerEmail: string

/** Contact Email to use for the Campaign Application process i.e. if more documents or other info are requested */
organizerPhone: string

/** Who will benefit and use the collected donations */
beneficiary: string

/** What is the relationship between the Organizer and the Beneficiary ('They're my elderly relative and I'm helping with the internet-computer stuff') */
organizerBeneficiaryRel: string

/** What is the result that the collected donations will help achieve */
goal: string

/** What if anything has been done so far */
history?: string

/** How much would the campaign be looking for i.e '10000lv or 5000 Eur or $5000' */
amount: string

/** Describe the goal of the campaign in more details */
description?: string

/** Describe public figures that will back the campaign and help popularize it. */
campaignGuarantee?: string

/** If any - describe what other sources were used to gather funds for the goal */
otherFinanceSources?: string

/** Anything that the operator needs to know about the campaign */
otherNotes?: string

category?: CampaignTypeCategory
}

export type CreateCampaignApplicationResponse = CreateCampaignApplicationInput & {
id: string
}
3 changes: 3 additions & 0 deletions src/service/apiEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,4 +424,7 @@ export const endpoints = {
}
},
},
campaignApplication: {
create: <Endpoint>{ url: '/campaign-application/create', method: 'POST' },
},
}
19 changes: 19 additions & 0 deletions src/service/campaign-application.ts
Original file line number Diff line number Diff line change
@@ -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<CreateCampaignApplicationResponse>
>(endpoints.campaignApplication.create.url, data, authConfig(session?.accessToken))
}

0 comments on commit 64d8c0d

Please sign in to comment.