diff --git a/public/locales/bg/profile.json b/public/locales/bg/profile.json index 404452d31..747bbc8bb 100644 --- a/public/locales/bg/profile.json +++ b/public/locales/bg/profile.json @@ -50,6 +50,12 @@ "index": "История на сертификати", "title": "Онлайн дарения" }, + "myCampaigns": { + "index": "Моите кампании", + "history": "Управление на моите кампании", + "noCampaigns": "Вие не сте в роля организатор, координатор или бенефицент към нито една кампания", + "donatedTo": "Кампании, в които съм дарил" + }, "donationsContract": "Договор дарение", "certificates": "Сертификати", "birthdateModal": { diff --git a/public/locales/en/profile.json b/public/locales/en/profile.json index 9bb7de7ba..30902097e 100644 --- a/public/locales/en/profile.json +++ b/public/locales/en/profile.json @@ -43,6 +43,12 @@ "index": "Certificate history", "title": "Online donations" }, + "myCampaigns": { + "index": "My campaigns", + "history": "Manage my campaigns", + "noCampaigns": "You are not in organizer, coordinator or beneficiery role in any campaign", + "donatedTo": "Campaigns I donated to" + }, "donationsContract": "Donation contract", "certificates": "Certificates", "birthdateModal": { diff --git a/src/common/hooks/campaigns.ts b/src/common/hooks/campaigns.ts index 08f878f92..5f16eb912 100644 --- a/src/common/hooks/campaigns.ts +++ b/src/common/hooks/campaigns.ts @@ -24,6 +24,21 @@ export function useCampaignAdminList() { ) } +export const useGetUserCampaigns = () => { + const { data: session } = useSession() + return useQuery( + endpoints.campaign.getUserCamapaigns.url, + authQueryFnFactory(session?.accessToken), + ) +} + +export function useUserDonationsCampaigns() { + const { data: session } = useSession() + return useQuery(endpoints.campaign.getUserDonatedToCampaigns.url, { + queryFn: authQueryFnFactory(session?.accessToken), + }) +} + export function useCampaignTypesList() { return useQuery(endpoints.campaignTypes.listCampaignTypes.url) } diff --git a/src/common/routes.ts b/src/common/routes.ts index b7808f39e..d4f52eb5c 100644 --- a/src/common/routes.ts +++ b/src/common/routes.ts @@ -57,6 +57,7 @@ export const routes = { personalInformation: '/profile/personal-information', certificates: '/profile/certificates', contractDonation: '/profile/contract-donation', + myCampaigns: '/profile/my-campaigns', }, register: '/register', aboutProject: '/about-project', diff --git a/src/components/auth/profile/MyCampaignsTab.tsx b/src/components/auth/profile/MyCampaignsTab.tsx new file mode 100644 index 000000000..4265d111d --- /dev/null +++ b/src/components/auth/profile/MyCampaignsTab.tsx @@ -0,0 +1,67 @@ +import { styled } from '@mui/material/styles' +import { Box, Typography } from '@mui/material' +import React from 'react' +import { useTranslation } from 'next-i18next' + +import ProfileTab from './ProfileTab' +import { ProfileTabs } from './tabs' +import MyCampaingsTable from './MyCampaignsTable' +import MyDonatedToCampaignTable from './MyDonatedToCampaignsTable' + +const PREFIX = 'MyCampaignsTab' + +const classes = { + h3: `${PREFIX}-h3`, + thinFont: `${PREFIX}-thinFont`, + smallText: `${PREFIX}-smallText`, + boxTitle: `${PREFIX}-boxTitle`, +} + +const Root = styled('div')(({ theme }) => ({ + [`& .${classes.h3}`]: { + fontStyle: 'normal', + fontWeight: '500', + fontSize: '25px', + lineHeight: '116.7%', + margin: '0', + }, + [`& .${classes.thinFont}`]: { + fontStyle: 'normal', + fontWeight: 400, + fontSize: '24px', + lineHeight: '123.5%', + letterSpacing: '0.25px', + color: '#000000', + margin: 0, + }, + [`& .${classes.smallText}`]: { + fontFamily: 'Lato, sans-serif', + fontStyle: 'normal', + fontWeight: '500', + fontSize: '15px', + lineHeight: '160%', + letterSpacing: '0.15px', + }, + [`& .${classes.boxTitle}`]: { + backgroundColor: 'white', + padding: theme.spacing(3, 7), + paddingBottom: theme.spacing(3), + marginTop: theme.spacing(3), + boxShadow: theme.shadows[3], + }, +})) + +export default function MyCampaignsTab() { + const { t } = useTranslation() + return ( + + + + {t('profile:myCampaigns.donatedTo')} + + + + + + ) +} diff --git a/src/components/auth/profile/MyCampaignsTable.tsx b/src/components/auth/profile/MyCampaignsTable.tsx new file mode 100644 index 000000000..5f2c24433 --- /dev/null +++ b/src/components/auth/profile/MyCampaignsTable.tsx @@ -0,0 +1,320 @@ +import { bg, enUS } from 'date-fns/locale' +import { useTranslation } from 'next-i18next' +import { useState, useMemo } from 'react' +import { DataGrid, GridColDef, GridColumns, GridRenderCellParams } from '@mui/x-data-grid' +import { Tooltip, Button, Box, Typography, styled } from '@mui/material' + +import { getExactDateTime, getRelativeDate } from 'common/util/date' +import { money } from 'common/util/money' +import { useGetUserCampaigns } from 'common/hooks/campaigns' +import Link from 'components/common/Link' +import GridActions from 'components/campaigns/grid/GridActions' +import { + DisplayBeneficiary, + DisplayBlockedAmount, + DisplayCoordinator, + DisplayCurrentAmount, + DisplayExpandableDescription, + DisplayOrganizer, + DisplayReachedAmount, +} from 'components/campaigns/grid/CampaignGrid' +import DetailsModal from '../../campaigns/grid/modals/DetailsModal' +import DeleteModal from '../../campaigns/grid/modals/DeleteModal' +import ProfileTab from './ProfileTab' +import { ProfileTabs } from './tabs' + +const PREFIX = 'MyCampaignsTab' + +const classes = { + h3: `${PREFIX}-h3`, + thinFont: `${PREFIX}-thinFont`, + smallText: `${PREFIX}-smallText`, + boxTitle: `${PREFIX}-boxTitle`, +} + +const Root = styled('div')(({ theme }) => ({ + [`& .${classes.h3}`]: { + fontStyle: 'normal', + fontWeight: '500', + fontSize: '25px', + lineHeight: '116.7%', + margin: '0', + }, + [`& .${classes.thinFont}`]: { + fontStyle: 'normal', + fontWeight: 400, + fontSize: '24px', + lineHeight: '123.5%', + letterSpacing: '0.25px', + color: '#000000', + margin: 0, + }, + [`& .${classes.smallText}`]: { + fontFamily: 'Lato, sans-serif', + fontStyle: 'normal', + fontWeight: '500', + fontSize: '15px', + lineHeight: '160%', + letterSpacing: '0.15px', + }, + [`& .${classes.boxTitle}`]: { + backgroundColor: 'white', + padding: theme.spacing(3, 7), + paddingBottom: theme.spacing(3), + marginTop: theme.spacing(3), + boxShadow: theme.shadows[3], + }, +})) + +export default function MyCampaingsTable() { + const { t, i18n } = useTranslation() + const locale = i18n.language == 'bg' ? bg : enUS + const [viewId, setViewId] = useState() + const [deleteId, setDeleteId] = useState() + const { data: campaigns = [], refetch } = useGetUserCampaigns() + const selectedCampaign = useMemo( + () => campaigns.find((c) => c.id === viewId), + [campaigns, viewId], + ) + const commonProps: Partial = { + align: 'left', + width: 100, + headerAlign: 'left', + } + const columns: GridColumns = [ + { + field: 'actions', + headerName: t('campaigns:actions'), + width: 120, + type: 'actions', + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => { + return ( + setViewId(cellValues.row.id)} + onDelete={() => setDeleteId(cellValues.row.id)} + /> + ) + }, + }, + { + field: 'state', + headerName: t('campaigns:status'), + ...commonProps, + align: 'left', + width: 120, + }, + { + field: 'title', + headerName: t('campaigns:title'), + ...commonProps, + align: 'left', + width: 350, + renderCell: (cellValues: GridRenderCellParams) => ( + {cellValues.row.title} + ), + }, + { + field: 'essence', + headerName: t('campaigns:essence'), + ...commonProps, + align: 'left', + width: 350, + }, + { + field: 'coordinator', + headerName: t('campaigns:coordinator'), + ...commonProps, + renderCell: (params: GridRenderCellParams) => { + return + }, + align: 'left', + width: 200, + }, + { + field: 'organizer', + headerName: t('campaigns:organizer'), + ...commonProps, + renderCell: (params: GridRenderCellParams) => { + return + }, + align: 'left', + width: 200, + }, + { + field: 'beneficiary', + headerName: t('campaigns:beneficiary'), + ...commonProps, + renderCell: (params: GridRenderCellParams) => { + return + }, + align: 'left', + width: 200, + }, + { + field: 'campaignType', + headerName: t('campaigns:campaignType'), + ...commonProps, + align: 'left', + width: 250, + renderCell: (cellValues: GridRenderCellParams) => <>{cellValues.row.campaignType.name}, + }, + { + field: 'description', + headerName: t('campaigns:description'), + ...commonProps, + align: 'left', + width: 350, + renderCell: DisplayExpandableDescription, + }, + { + field: 'reachedAmount', + headerName: t('campaigns:donationsAmount'), + ...commonProps, + align: 'right', + width: 200, + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'blockedAmount', + headerName: t('campaigns:blockedAmount'), + ...commonProps, + align: 'right', + width: 200, + renderCell: (cellValues: GridRenderCellParams) => ( + + ), + }, + { + field: 'currentAmount', + headerName: t('campaigns:amount'), + ...commonProps, + align: 'right', + width: 200, + renderCell: (cellValues: GridRenderCellParams) => ( + + ), + }, + { + field: 'targetAmount', + headerName: t('campaigns:targetAmount'), + ...commonProps, + align: 'right', + width: 150, + renderCell: (cellValues: GridRenderCellParams) => ( + <>{money(cellValues.row.targetAmount, cellValues.row.currency)} + ), + }, + { + field: 'currency', + headerName: t('campaigns:currency'), + ...commonProps, + align: 'left', + }, + { + field: 'startDate', + headerName: t('campaigns:startDate'), + align: 'left', + width: 230, + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'endDate', + headerName: t('campaigns:endDate'), + align: 'left', + width: 230, + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'createdAt', + headerName: t('campaigns:createDate'), + align: 'left', + width: 230, + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'updatedAt', + headerName: t('campaigns:updatedAt'), + align: 'left', + width: 230, + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'deletedAt', + headerName: t('campaigns:deletedAt'), + align: 'left', + width: 230, + headerAlign: 'left', + }, + ] + return ( + <> + {campaigns.length !== 0 ? ( + <> + + {t('profile:myCampaigns.history')} + + + + + + ) : null} + + {selectedCampaign && ( + setViewId(undefined)} /> + )} + {deleteId && ( + { + refetch() + setDeleteId(undefined) + }} + onClose={() => setDeleteId(undefined)} + /> + )} + + + ) +} diff --git a/src/components/auth/profile/MyDonatedToCampaignsTable.tsx b/src/components/auth/profile/MyDonatedToCampaignsTable.tsx new file mode 100644 index 000000000..73633e13d --- /dev/null +++ b/src/components/auth/profile/MyDonatedToCampaignsTable.tsx @@ -0,0 +1,210 @@ +import { bg, enUS } from 'date-fns/locale' +import { useTranslation } from 'next-i18next' +import { DataGrid, GridColDef, GridColumns, GridRenderCellParams } from '@mui/x-data-grid' +import { Tooltip, Button, Box } from '@mui/material' + +import { getExactDateTime, getRelativeDate } from 'common/util/date' +import { money } from 'common/util/money' +import { useUserDonationsCampaigns } from 'common/hooks/campaigns' +import Link from 'components/common/Link' +import { + DisplayBeneficiary, + DisplayCoordinator, + DisplayCurrentAmount, + DisplayExpandableDescription, + DisplayOrganizer, + DisplayReachedAmount, +} from 'components/campaigns/grid/CampaignGrid' + +export default function MyDonatedToCampaignTable() { + const { t, i18n } = useTranslation() + const locale = i18n.language == 'bg' ? bg : enUS + const { data = [], refetch } = useUserDonationsCampaigns() + console.log(data) + const commonProps: Partial = { + align: 'left', + width: 100, + headerAlign: 'left', + } + console.log(data) + const columns: GridColumns = [ + { + field: 'state', + headerName: t('campaigns:status'), + ...commonProps, + align: 'left', + width: 130, + }, + { + field: 'title', + headerName: t('campaigns:title'), + ...commonProps, + align: 'left', + width: 380, + renderCell: (cellValues: GridRenderCellParams) => ( + {cellValues.row.title} + ), + }, + { + field: 'essence', + headerName: t('campaigns:essence'), + ...commonProps, + align: 'left', + width: 380, + }, + { + field: 'coordinator', + headerName: t('campaigns:coordinator'), + ...commonProps, + renderCell: (params: GridRenderCellParams) => { + return + }, + align: 'left', + width: 230, + }, + { + field: 'organizer', + headerName: t('campaigns:organizer'), + ...commonProps, + renderCell: (params: GridRenderCellParams) => { + return + }, + align: 'left', + width: 230, + }, + { + field: 'beneficiary', + headerName: t('campaigns:beneficiary'), + ...commonProps, + renderCell: (params: GridRenderCellParams) => { + return + }, + align: 'left', + width: 230, + }, + { + field: 'campaignType', + headerName: t('campaigns:campaignType'), + ...commonProps, + align: 'left', + width: 280, + renderCell: (cellValues: GridRenderCellParams) => <>{cellValues.row.campaignType.name}, + }, + { + field: 'description', + headerName: t('campaigns:description'), + ...commonProps, + align: 'left', + width: 380, + renderCell: DisplayExpandableDescription, + }, + { + field: 'reachedAmount', + headerName: t('campaigns:donationsAmount'), + ...commonProps, + align: 'right', + width: 250, + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'targetAmount', + headerName: t('campaigns:targetAmount'), + ...commonProps, + align: 'right', + width: 180, + renderCell: (cellValues: GridRenderCellParams) => ( + <>{money(cellValues.row.targetAmount, cellValues.row.currency)} + ), + }, + { + field: 'currency', + headerName: t('campaigns:currency'), + ...commonProps, + align: 'left', + }, + { + field: 'startDate', + headerName: t('campaigns:startDate'), + align: 'left', + width: 270, + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'endDate', + headerName: t('campaigns:endDate'), + align: 'left', + width: 270, + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'createdAt', + headerName: t('campaigns:createDate'), + align: 'left', + width: 270, + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'updatedAt', + headerName: t('campaigns:updatedAt'), + align: 'left', + width: 270, + headerAlign: 'left', + renderCell: (cellValues: GridRenderCellParams) => ( + + + + ), + }, + { + field: 'deletedAt', + headerName: t('campaigns:deletedAt'), + align: 'left', + width: 270, + headerAlign: 'left', + }, + ] + return ( + <> + {data.length !== 0 ? ( + + ) : ( + {t('profile:donations.noDonations')} + )} + + ) +} diff --git a/src/components/auth/profile/ProfilePage.tsx b/src/components/auth/profile/ProfilePage.tsx index 366a9b9e6..97f0e5b2b 100644 --- a/src/components/auth/profile/ProfilePage.tsx +++ b/src/components/auth/profile/ProfilePage.tsx @@ -9,6 +9,7 @@ import { VolunteerActivism as DonationIcon, AccountBox as AccountBoxIcon, Assignment as CertificateIcon, + AssignmentInd as CampaignIcon, } from '@mui/icons-material' import { useSession } from 'next-auth/react' @@ -106,6 +107,14 @@ export default function ProfilePage() { onClick={() => router.push(routes.profile.certificates)} icon={matches ? : undefined} /> + router.push(routes.profile.myCampaigns)} + icon={matches ? : undefined} + /> {/* Currently we don't generate donation contract, when such document is generated we can either combine it with the certificate or unhide the contracts section. */} {/* } -const DisplayCoordinator = ({ params }: CampaignCellProps) => { +export const DisplayCoordinator = ({ params }: CampaignCellProps) => { return ( <> {params.row.coordinator.person.firstName} {params.row.coordinator.person.lastName} ) } -const DisplayOrganizer = ({ params }: CampaignCellProps) => { +export const DisplayOrganizer = ({ params }: CampaignCellProps) => { return ( <> {params.row.organizer?.person.firstName || ''} {params.row.organizer?.person.lastName || ''} @@ -38,7 +38,7 @@ const DisplayOrganizer = ({ params }: CampaignCellProps) => { ) } -const DisplayBeneficiary = ({ params }: CampaignCellProps) => { +export const DisplayBeneficiary = ({ params }: CampaignCellProps) => { return ( <> {params.row.beneficiary.type === BeneficiaryType.individual @@ -48,19 +48,19 @@ const DisplayBeneficiary = ({ params }: CampaignCellProps) => { ) } -const DisplayExpandableDescription = (params: GridRenderCellParams) => { +export const DisplayExpandableDescription = (params: GridRenderCellParams) => { return } -const DisplayReachedAmount = ({ params }: CampaignCellProps) => { +export const DisplayReachedAmount = ({ params }: CampaignCellProps) => { return <>{money(params.row.summary.reachedAmount ?? 0, params.row.currency)} } -const DisplayBlockedAmount = ({ params }: CampaignCellProps) => { +export const DisplayBlockedAmount = ({ params }: CampaignCellProps) => { return <>{money(params.row.summary.blockedAmount ?? 0, params.row.currency)} } -const DisplayCurrentAmount = ({ params }: CampaignCellProps) => { +export const DisplayCurrentAmount = ({ params }: CampaignCellProps) => { return <>{money(params.row.summary.currentAmount ?? 0, params.row.currency)} } diff --git a/src/pages/profile/[slug].ts b/src/pages/profile/[slug].ts index dba7f75f7..7ad5c68db 100644 --- a/src/pages/profile/[slug].ts +++ b/src/pages/profile/[slug].ts @@ -6,6 +6,7 @@ export const getServerSideProps = securedPropsWithTranslation([ 'profile', 'common', 'validation', + 'campaigns', ]) export default ProfilePage diff --git a/src/pages/profile/index.ts b/src/pages/profile/index.ts index 3754df011..712a5920e 100644 --- a/src/pages/profile/index.ts +++ b/src/pages/profile/index.ts @@ -6,6 +6,7 @@ export const getServerSideProps = securedPropsWithTranslation([ 'profile', 'common', 'validation', + 'campaigns', ]) export default ProfilePage diff --git a/src/service/apiEndpoints.ts b/src/service/apiEndpoints.ts index f32cecd6b..c4c7dff35 100644 --- a/src/service/apiEndpoints.ts +++ b/src/service/apiEndpoints.ts @@ -16,7 +16,12 @@ export const endpoints = { campaign: { listCampaigns: { url: '/campaign/list', method: 'GET' }, listAdminCampaigns: { url: '/campaign/list-all', method: 'GET' }, + getUserDonatedToCampaigns: { + url: '/campaign/user-donations-campaigns', + method: 'GET', + }, createCampaign: { url: '/campaign/create-campaign', method: 'POST' }, + getUserCamapaigns: { url: '/campaign/get-user-campaigns', method: 'GET' }, viewCampaign: (slug: string) => { url: `/campaign/${slug}`, method: 'GET' }, viewCampaignById: (id: string) => { url: `/campaign/byId/${id}`, method: 'GET' }, editCampaign: (id: string) => { url: `/campaign/${id}`, method: 'PUT' },