From c476493c23419ba54621b83ecbb83741cd14dc0e Mon Sep 17 00:00:00 2001 From: wraeth-eth <104132113+wraeth-eth@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:53:10 +1100 Subject: [PATCH] Enhance v4 project activity to support pagination and filtering by event type (#4564) --- .../V4ActivityPanel/V4ActivityList.tsx | 144 ++++++++++++++---- .../utils/transformEventsData.ts | 2 +- 2 files changed, 116 insertions(+), 30 deletions(-) diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ActivityPanel/V4ActivityList.tsx b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ActivityPanel/V4ActivityList.tsx index 868b84e2fb..f193cc9e5f 100644 --- a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ActivityPanel/V4ActivityList.tsx +++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ActivityPanel/V4ActivityList.tsx @@ -1,65 +1,124 @@ +import { useInfiniteQuery } from '@tanstack/react-query' +import { Button } from 'antd' +import { JuiceListbox } from 'components/inputs/JuiceListbox' import Loading from 'components/Loading' import RichNote from 'components/RichNote/RichNote' -import { NativeTokenValue, useJBContractContext } from 'juice-sdk-react' +import request from 'graphql-request' +import { + NativeTokenValue, + useJBChainId, + useJBContractContext, +} from 'juice-sdk-react' +import { v4SubgraphUri } from 'lib/apollo/subgraphUri' +import { last } from 'lodash' import { useProjectContext } from 'packages/v2v3/components/V2V3Project/ProjectDashboard/hooks/useProjectContext' import { OrderDirection, ProjectEvent_OrderBy, ProjectEventsDocument, } from 'packages/v4/graphql/client/graphql' -import { useSubgraphQuery } from 'packages/v4/graphql/useSubgraphQuery' import React from 'react' import { tokenSymbolText } from 'utils/tokenSymbolText' import { ActivityEvent } from './activityEventElems/ActivityElement' -import { AnyEvent, transformEventData } from './utils/transformEventsData' +import { + AnyEvent, + EventType, + transformEventData, +} from './utils/transformEventsData' + +const PAGE_SIZE = 10 export function V4ActivityList() { const { projectId } = useJBContractContext() const tokenSymbol = useProjectContext().tokenSymbol - const { data: projectEventsData, isLoading } = useSubgraphQuery({ - document: ProjectEventsDocument, - variables: { - orderBy: ProjectEvent_OrderBy.timestamp, - orderDirection: OrderDirection.desc, - where: { - projectId: Number(projectId), - }, + const [filter, setFilter] = React.useState('all') + + const chainId = useJBChainId() + const { + data: projectEventsQueryResult, + isLoading, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: ['projectEvents', projectId, filter], + initialPageParam: 0, + queryFn: async ({ pageParam = 0 }) => { + if (!chainId) { + throw new Error('useSubgraphQuery needs a chainId, none provided') + } + const uri = v4SubgraphUri(chainId) + const document = ProjectEventsDocument + + const data = await request(uri, document, { + orderBy: ProjectEvent_OrderBy.timestamp, + orderDirection: OrderDirection.desc, + where: { + projectId: Number(projectId), + // ProjectEvents have exactly one non-null Event field. We can use `_not: null` to return only projectEvents where the matching Event field is defined + ...(!filter || filter === 'all' + ? {} + : { + [filter + '_not']: null, + }), + }, + skip: pageParam * PAGE_SIZE, + first: PAGE_SIZE, + }) + const mightHaveNextPage = data.projectEvents.length === PAGE_SIZE + return { + data, + nextCursor: mightHaveNextPage ? pageParam + PAGE_SIZE : undefined, + } + }, + getNextPageParam: lastPage => { + return lastPage.nextCursor }, }) const projectEvents = React.useMemo( () => - projectEventsData?.projectEvents + projectEventsQueryResult?.pages + .flatMap(page => page.data.projectEvents) .map(transformEventData) .filter((event): event is AnyEvent => !!event) .map(e => translateEventDataToPresenter(e, tokenSymbol)) ?? [], - [projectEventsData?.projectEvents, tokenSymbol], + [projectEventsQueryResult?.pages, tokenSymbol], ) return (
-
+

Activity

+ o.value === filter)} + onChange={o => setFilter(o.value as ProjectEventFilter)} + /> {isLoading && } {isLoading || (projectEvents && projectEvents.length > 0) ? ( - projectEvents?.map(event => { - return ( -
- -
- ) - }) + <> + {projectEvents?.map(event => { + return ( +
+ +
+ ) + })} + {!!last(projectEventsQueryResult?.pages)?.nextCursor && ( + + )} + ) : ( No activity yet. )} @@ -226,3 +285,30 @@ function translateEventDataToPresenter( } } } + +type ProjectEventFilter = 'all' | EventType + +const ACTIVITY_OPTIONS = [ + { label: 'All activity', value: 'all' }, + { label: 'Paid', value: 'payEvent' }, + { label: 'Added to balance', value: 'addToBalanceEvent' }, + { label: 'Minted tokens', value: 'mintTokensEvent' }, + { label: 'Cashed out', value: 'cashOutEvent' }, + { label: 'Deployed ERC20', value: 'deployedERC20Event' }, + { label: 'Project created', value: 'projectCreateEvent' }, + { label: 'Distributed payouts', value: 'distributePayoutsEvent' }, + { + label: 'Distributed reserved tokens', + value: 'distributeReservedTokensEvent', + }, + { + label: 'Distributed to reserved token split', + value: 'distributeToReservedTokenSplitEvent', + }, + { + label: 'Distributed to payout split', + value: 'distributeToPayoutSplitEvent', + }, + { label: 'Used allowance', value: 'useAllowanceEvent' }, + { label: 'Burned', value: 'burnEvent' }, +] diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ActivityPanel/utils/transformEventsData.ts b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ActivityPanel/utils/transformEventsData.ts index 54a05b0235..bc8af6dadc 100644 --- a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ActivityPanel/utils/transformEventsData.ts +++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ActivityPanel/utils/transformEventsData.ts @@ -1,7 +1,7 @@ import { Ether, JBProjectToken } from 'juice-sdk-core' import { ProjectEventsQuery } from 'packages/v4/graphql/client/graphql' -type EventType = +export type EventType = | 'payEvent' | 'addToBalanceEvent' | 'mintTokensEvent'