Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create campaign application #1893

Merged
merged 2 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
}
Loading