Skip to content

Commit

Permalink
Campaign Applications (alpha) (#1930)
Browse files Browse the repository at this point in the history
* feat: file upload and summary

- handle file upload one by one to allow for handling errors individually (otherwise the server just returns response failed for any one file that fails and no information about the successful uploads)
- add summary where the uploaded and failed (if any) files are top and center
- add a short  explanation of what to do about failed files

* feat: validation of create campaign application

* fix: send campaignTypeId + editing + visuals

- send campaignTypeId instead of the category (which is a property of the campaign type and is not enough to uniquely distinguish the type)
- allow for editing and re-editing
- style the summary
- fix linting errors

* chore: test create application

- moved the useCreateOrEditApplication hook to own file to reuse for the admin campaign edit
- added campaign end date handling (if the user selects a date for the end it pops up an input and fills in the preselected by user date or today if none)
- removed some unused props from the cam app details
- added msw v1 https://v1.mswjs.io/docs/getting-started/mocks/rest-api to mock the server responses for testing (Mock Service Worker)
- expanded the test setup to provide Session and QueryClient

* chore: add tests for the createOrEdit application hook

* feat: admin edit

- it uses the same controls/steps as the organizer edit but adds the admin only props on top
- adds a link and copy button for the link where organizer can edit so as to include it if sending a mail to the org (e.g. for more info)
- deletes the admin types - using same as regular
- adds the status dropdown for admin edit
- allows the accept terms checkboxes to be disabled for admin

* feat: actual list of campaign applications

* fix: small fixes

* fix: pre-mature validation on basic step

* fix: missing method
  • Loading branch information
gparlakov committed Sep 25, 2024
1 parent 6dd016e commit 3a912bf
Show file tree
Hide file tree
Showing 35 changed files with 1,991 additions and 450 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"jest": "^29.6.1",
"jest-environment-jsdom": "^29.6.1",
"lint-staged": "11.0.0",
"msw": "1",
"next-sitemap": "^3.1.52",
"prettier": "2.3.0",
"shx": "^0.3.3",
Expand Down
29 changes: 26 additions & 3 deletions public/locales/bg/campaign-application.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@
}
},
"photos": "Снимков/видео материал",
"documents": "Документи"
"documents": "Документи",
"disclaimer": "Приемат се до 10 документа. Всеки от документите може да бъде до 30МБ"
},
"admin": {
"title": "Администраторска редакция",
"status": "Статус",
"external-url": "Външен линк"
"external-url": "Външен линк",
"archived": "Архивиран",
"organizer-edit-link": "Организатор може да редактира на"
}
},
"cta": {
Expand All @@ -62,6 +65,26 @@
}
},
"alerts": {
"successfully-created": "Успешно създадена кампания. "
"successfully-created": "Успешно създадена заявка за кампания. "
},
"result": {
"created": "Успешно създадена заявка за кампания.",
"edited": "Успешно редактирана заявка за кампания.",
"editButton": "Редакция на заявка за кампания",
"uploadOk": "Добавени файлове",
"deleteOk": "Премахнати файлове",
"uploadFailed": "Неуспешно добавени файлове",
"deleteFailed": "Неуспешно премахнати файлове",
"uploadFailedWhat": "Какво мога да направя?",
"uploadFailedDirection": "Моля посетете страницата за редакция на заявката за кампания от бутона по-долу и опитайте отново да добавите/премахнете файловете"
},
"status": {
"selectorLabel": "Статус",
"review": "Нова за ревю",
"requestInfo": "За още информация",
"forCommitteeReview": "За ревю от комисия",
"approved": "Одобрена",
"denied": "Отказана",
"abandoned": "Изоставена"
}
}
27 changes: 25 additions & 2 deletions public/locales/en/campaign-application.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@
}
},
"photos": "Photo/Video material",
"documents": "Documents"
"documents": "Documents",
"disclaimer": "Up to 10 documents accepted. Each of the documents can be up to 30MB is size"
},
"admin": {
"title": "Admin edit",
"status": "Status",
"external-url": "External URL"
"external-url": "External URL",
"archived": "Archived",
"organizer-edit-link": "Organizer can edit at"
}
},
"cta": {
Expand All @@ -63,5 +66,25 @@
},
"alerts": {
"successfully-created": "Campaign application successfully created."
},
"result": {
"created": "Successfully created campaign application",
"edited": "Successfully edited campaign application",
"editButton": "Edit campaign application",
"uploadOk": "Fails added",
"deleteOk": "Files removed",
"uploadFailed": "Failed file add",
"deleteFailed": "Failed to remove files",
"uploadFailedWhat": "What can I do?",
"uploadFailedDirection": "Please visit the campaign application edit page linked below and retry adding/removing the files."
},
"status": {
"selectorLabel": "Status",
"review": "New - for review",
"requestInfo": "Request for more info",
"forCommitteeReview": "For committee review",
"approved": "Approved",
"denied": "Denied",
"abandoned": "Abandoned"
}
}
1 change: 1 addition & 0 deletions src/common/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export const routes = {
index: '/campaigns',
create: '/campaigns/create',
application: 'campaigns/application',
applicationEdit: (id: string) => `/campaigns/application/${id}`,
viewCampaignBySlug: (slug: string) => `/campaigns/${slug}`,
viewExpenses: (slug: string) => `/campaigns/${slug}/expenses`,
oneTimeDonation: (slug: string) => `/campaigns/donation/${slug}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
import { useTranslation } from 'next-i18next'

import { Grid } from '@mui/material'
import { StatusSelector } from 'components/client/campaign-application/helpers/campaign-application-status'
import {
StyledFormTextField,
StyledStepHeading,
} from 'components/client/campaign-application/helpers/campaignApplication.styled'
import { CamAppDetail } from 'components/client/campaign-application/steps/CampaignApplicationSummary'
import CheckboxField from 'components/common/form/CheckboxField'
import OrganizerCanEditAt from './CampaignApplicationOrganizerCanEditAt'

export default function CampaignApplicationAdminPropsEdit() {
export default function CampaignApplicationAdminPropsEdit({ id }: { id: string }) {
const { t } = useTranslation('campaign-application')

return (
<Grid container spacing={6} justifyContent="center" direction="column" alignContent="center">
<Grid item container justifyContent="center">
<StyledStepHeading variant="h4">{t('steps.admin.title')}</StyledStepHeading>
</Grid>
<Grid item container spacing={6} justifyContent="space-between" direction="row">
<Grid item xs={12}>
<StyledFormTextField label={t('steps.admin.status')} type="text" name="status" />
<Grid item xs={6}>
<StatusSelector name="admin.state" />
</Grid>
<Grid item xs={6}>
<CheckboxField label={t('steps.admin.archived')} name="admin.archived" />
</Grid>
</Grid>
<Grid item container spacing={6} justifyContent="space-between" direction="row">
<Grid container item xs={12} md={6}>
<Grid container item xs={12}>
<StyledFormTextField
label={t('steps.admin.external-url')}
type="phone"
name="ticketUrl"
type="text"
name="admin.ticketURL"
/>
</Grid>
</Grid>
<Grid item container spacing={6} justifyContent="space-between" direction="row" mb="20px">
<CamAppDetail
label={t('steps.admin.organizer-edit-link')}
value={<OrganizerCanEditAt id={id} />}
/>
</Grid>
</Grid>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { routes } from 'common/routes'
import { CopyTextButton } from 'components/common/CopyTextButton'
import getConfig from 'next/config'
import Copy from '@mui/icons-material/CopyAll'
import { Typography } from '@mui/material'
export type Props = {
id: string
}
const OrganizerCanEditAt = ({ id }: Props) => {
const { publicRuntimeConfig } = getConfig()
const url = `${publicRuntimeConfig?.APP_URL}${routes.campaigns.applicationEdit(id)}`

return (
<>
<Typography variant="subtitle1" component="span">
{url}
</Typography>
<CopyTextButton label="" startIcon={<Copy />} text={url} title={`Copy ${url}`} />
</>
)
}

export default OrganizerCanEditAt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { useTranslation } from 'next-i18next'

import { DataGrid, GridColDef, GridRenderCellParams } from '@mui/x-data-grid'
import { useQuery } from '@tanstack/react-query'
import { routes } from 'common/routes'
import theme from 'common/theme'
import { CampaignApplicationAdminResponse } from 'gql/campaign-applications'
import { useSession } from 'next-auth/react'
import Link from 'next/link'
import { endpoints } from 'service/apiEndpoints'
import { authQueryFnFactory } from 'service/restRequests'

export default function CampaignApplicationsGrid() {
const { t, i18n } = useTranslation()
const { t } = useTranslation('campaign-application')
const { list } = useCampaignsList()

const commonProps: Partial<GridColDef> = {
align: 'left',
Expand All @@ -16,33 +22,34 @@ export default function CampaignApplicationsGrid() {

const columns: GridColDef[] = [
{
field: 'status',
field: 'state',
headerName: t('campaigns:status'),
...commonProps,
align: 'left',
width: 220,
renderCell: (cellValues: GridRenderCellParams) => t(`status.${cellValues.row.state}`),
},
{
field: 'title',
field: 'campaignName',
headerName: t('campaigns:title'),
...commonProps,
align: 'left',
width: 250,
renderCell: (cellValues: GridRenderCellParams) => (
<Link href={routes.admin.campaignApplications.edit(cellValues.id.toString())}>
{cellValues.row.title}
{cellValues.row.campaignName}
</Link>
),
},
{
field: 'essence',
field: 'goal',
headerName: t('campaigns:essence'),
...commonProps,
align: 'left',
width: 250,
},
{
field: 'organizer',
field: 'organizerName',
headerName: t('campaigns:organizer'),
...commonProps,
align: 'left',
Expand Down Expand Up @@ -71,76 +78,44 @@ export default function CampaignApplicationsGrid() {
},
]

const data = [
{
updatedAt: 'date',
createdAt: '2024-5-5',
beneficiary: 'beneficiary',
organizer: 'organizer',
essence: 'essence',
title: 'title',
id: '1',
status: 'нова',
},
{
updatedAt: 'yesterday',
createdAt: '10 days ago',
beneficiary: 'beneficiary',
organizer: 'organizer',
essence: 'essence',
title: 'title',
id: '2',
status: 'очаква документи',
},
{
updatedAt: '',
createdAt: '',
beneficiary: 'beneficiary',
organizer: 'organizer',
essence: 'essence',
title: 'title',
id: '3',
status: 'очаква решение на комисия',
},

{
updatedAt: '',
createdAt: '',
beneficiary: 'beneficiary',
organizer: 'organizer',
essence: 'essence',
title: 'title',
id: '4',
status: 'одобрена',
},
{
updatedAt: '',
createdAt: '',
beneficiary: 'beneficiary',
organizer: 'organizer',
essence: 'essence',
title: 'title',
id: '4',
status: 'отказана',
},
]
return (
<DataGrid
style={{
background: theme.palette.common.white,
position: 'absolute',
height: 'calc(100vh - 300px)',
height: 'calc(100vh - 299px)',
border: 'none',
width: 'calc(100% - 48px)',
left: '24px',
overflowY: 'auto',
overflowX: 'hidden',
borderRadius: '0 0 13px 13px',
}}
rows={data || []}
rows={list || []}
columns={columns}
editMode="row"
pageSizeOptions={[20, 50, 100]}
/>
)
}

function fetchMutation() {
const { data } = useSession()
return useQuery(
[endpoints.campaignApplication.listAllForAdmin.url],
authQueryFnFactory<CampaignApplicationAdminResponse[]>(data?.accessToken),
{
cacheTime: 10 * 60 * 1000,
staleTime: 10 * 60 * 1000,
},
)
}

export const useCampaignsList = () => {
const { data, isLoading } = fetchMutation()

return {
list: data?.sort((a, b) => b?.updatedAt?.localeCompare(a?.updatedAt ?? '') ?? 0),
isLoading,
}
}
Loading

0 comments on commit 3a912bf

Please sign in to comment.