diff --git a/src/modules/pointsVault/components/PointsVaultApprovedList.tsx b/src/modules/pointsVault/components/PointsVaultApprovedList.tsx index 77828e9522..fd4b7d07af 100644 --- a/src/modules/pointsVault/components/PointsVaultApprovedList.tsx +++ b/src/modules/pointsVault/components/PointsVaultApprovedList.tsx @@ -1,16 +1,15 @@ -import { Box } from 'blocks'; +import { Box, Pagination } from 'blocks'; import { useQueryClient } from '@tanstack/react-query'; -import InfiniteScroll from 'react-infinite-scroller'; import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; import { PointsVaultListColumns } from './PointsVaultListColumns'; import { PointsVaultListItem } from './PointsVaultListItem'; import { - PointsVaultActivitiesResponse, pointsVaultRejectedUsers, - useGetPointsVaultApprovedUsers, + useGetPointsVaultApprovedUsersPaginated, usePointsVaultToken, } from 'queries'; import { LeaderBoardNullState } from 'modules/rewards/components/LeaderboardNullState'; +import { useEffect, useState } from 'react'; type PointsVaultApprovedListProps = { query: { @@ -21,26 +20,28 @@ type PointsVaultApprovedListProps = { const PointsVaultApprovedList = ({ query }: PointsVaultApprovedListProps) => { const token = usePointsVaultToken(); - const queryClient = useQueryClient(); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, refetch } = - useGetPointsVaultApprovedUsers({ - status: 'COMPLETED', - token, - pageSize: 20, - twitter: query.twitter, - wallet: query.wallet, - activityTypeId: 'follow_push_on_twitter', - }); + const { data, isLoading, isError, refetch, isFetching } = useGetPointsVaultApprovedUsersPaginated({ + status: 'COMPLETED', + token, + pageSize, + page: currentPage, + twitter: query.twitter, + wallet: query.wallet, + activityTypeId: 'follow_push_on_twitter', + }); - const hasMoreData = !isFetchingNextPage && hasNextPage; + useEffect(() => { + setCurrentPage(1); + }, [query.twitter, query.wallet]); - const pointsVaultList = isLoading - ? Array(5).fill(0) - : data?.pages.flatMap((page: PointsVaultActivitiesResponse) => page.activities) || []; + const pointsVaultList = isLoading ? Array(5).fill(0) : data?.activities || []; + const totalItems = data?.total || 0; - if (!pointsVaultList.length) { + if (!pointsVaultList.length && !isLoading) { return ( { queryClient.invalidateQueries({ queryKey: [pointsVaultRejectedUsers] }); }; + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + return ( { flexDirection="column" > - fetchNextPage()} - hasMore={hasMoreData} - loader={ - - - - } - useWindow={false} - threshold={150} - > - {pointsVaultList.map((item, index) => ( - ( + + ))} + {isFetching && !isLoading && ( + + - ))} - + + )} + {totalItems > 0 && ( + + + + )} ); }; diff --git a/src/modules/pointsVault/components/PointsVaultListColumns.tsx b/src/modules/pointsVault/components/PointsVaultListColumns.tsx index f9c1fc2f0b..fa5099ae87 100644 --- a/src/modules/pointsVault/components/PointsVaultListColumns.tsx +++ b/src/modules/pointsVault/components/PointsVaultListColumns.tsx @@ -7,7 +7,7 @@ const PointsVaultListColumns: FC = () => { display="flex" justifyContent="space-between" > - + { - + { @@ -34,7 +34,20 @@ const PointsVaultListColumns: FC = () => { variant="c-bold" color="text-tertiary" > - FOLLOWERS + DISCORD USERNAME + + + + + + EMAIL diff --git a/src/modules/pointsVault/components/PointsVaultListContainer.tsx b/src/modules/pointsVault/components/PointsVaultListContainer.tsx index 19b07bfa57..06b2061eb8 100644 --- a/src/modules/pointsVault/components/PointsVaultListContainer.tsx +++ b/src/modules/pointsVault/components/PointsVaultListContainer.tsx @@ -7,6 +7,7 @@ import { PointsVaultRejectedList } from './PointsVaultRejectedList'; import { useDebounce } from 'react-use'; import { useCallback, useState } from 'react'; import { ethers } from 'ethers'; +import { useGetUserRewardsStats } from 'queries'; const PointsVaultListContainer = () => { const [query, setQuery] = useState(''); @@ -22,18 +23,70 @@ const PointsVaultListContainer = () => { useDebounce(() => setDebouncedQuery(getFormattedQuery(query)), 500, [query]); + const { data: userStats } = useGetUserRewardsStats(); + return ( - - Points Vault - + + Points Vault + + {userStats && ( + + + + {userStats.totalUsers.toLocaleString()} + + + Total Users + + + + + {userStats.totalUsersWithEmail.toLocaleString()} + + + With Email + + + + )} + void; }; +const truncateMiddle = (str: string, startChars: number = 14, endChars: number = 14) => { + if (!str) return ''; + if (str.length <= startChars + endChars) return str; + return `${str.slice(0, startChars)}...${str.slice(-endChars)}`; +}; + const PointsVaultListItem = ({ isLoading, item, refetch }: PointsVaultListItemProps) => { const token = usePointsVaultToken(); const { data } = useGetUserTwitterDetails(item.data?.twitter, token); @@ -28,14 +34,14 @@ const PointsVaultListItem = ({ isLoading, item, refetch }: PointsVaultListItemPr - {caip10ToWallet(item.userWallet)} + {truncateMiddle(item.userWallet)} @@ -44,10 +50,10 @@ const PointsVaultListItem = ({ isLoading, item, refetch }: PointsVaultListItemPr @@ -55,7 +61,7 @@ const PointsVaultListItem = ({ isLoading, item, refetch }: PointsVaultListItemPr color="text-brand-medium" variant="bs-semibold" > - https://x.com/{item.data?.twitter} + https://x.com/{item.twitterUserName || item.data?.twitter} @@ -66,13 +72,27 @@ const PointsVaultListItem = ({ isLoading, item, refetch }: PointsVaultListItemPr display="flex" alignItems="center" justifyContent="center" - width="42px" + width="190px" + > + + {item?.primaryDiscordUserName ?? '-'} + + + + + + - {data?.followersCount ?? '-'} + {item?.discordEmail ?? '-'} diff --git a/src/modules/pointsVault/components/PointsVaultPendingList.tsx b/src/modules/pointsVault/components/PointsVaultPendingList.tsx index 96b16ae3a7..6634c41ced 100644 --- a/src/modules/pointsVault/components/PointsVaultPendingList.tsx +++ b/src/modules/pointsVault/components/PointsVaultPendingList.tsx @@ -1,18 +1,17 @@ -import { Box } from 'blocks'; +import { Box, Pagination } from 'blocks'; import { useQueryClient } from '@tanstack/react-query'; -import InfiniteScroll from 'react-infinite-scroller'; import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; import { PointsVaultListColumns } from './PointsVaultListColumns'; import { PointsVaultListItem } from './PointsVaultListItem'; import { - PointsVaultActivitiesResponse, PointsVaultStatus, pointsVaultApprovedUsers, pointsVaultRejectedUsers, - useGetPointsVaultPendingUsers, + useGetPointsVaultPendingUsersPaginated, usePointsVaultToken, } from 'queries'; import { LeaderBoardNullState } from 'modules/rewards/components/LeaderboardNullState'; +import { useEffect, useState } from 'react'; type PointsVaultPendingListProps = { query: { @@ -23,26 +22,29 @@ type PointsVaultPendingListProps = { const PointsVaultPendingList = ({ query }: PointsVaultPendingListProps) => { const token = usePointsVaultToken(); - const queryClient = useQueryClient(); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, refetch } = - useGetPointsVaultPendingUsers({ - status: 'PENDING', - token, - pageSize: 20, - twitter: query.twitter, - wallet: query.wallet, - activityTypeId: 'follow_push_on_twitter', - }); + const { data, isLoading, isError, refetch, isFetching } = useGetPointsVaultPendingUsersPaginated({ + status: 'PENDING', + token, + pageSize, + page: currentPage, + twitter: query.twitter, + wallet: query.wallet, + activityTypeId: 'follow_push_on_twitter', + }); - const hasMoreData = !isFetchingNextPage && hasNextPage; + // Reset to page 1 when search query changes + useEffect(() => { + setCurrentPage(1); + }, [query.twitter, query.wallet]); - const pointsVaultList = isLoading - ? Array(5).fill(0) - : data?.pages.flatMap((page: PointsVaultActivitiesResponse) => page.activities) || []; + const pointsVaultList = isLoading ? Array(5).fill(0) : data?.activities || []; + const totalItems = data?.total || 0; - if (!pointsVaultList.length) { + if (!pointsVaultList.length && !isLoading) { return ( { } }; + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + return ( { flexDirection="column" > - fetchNextPage()} - hasMore={hasMoreData} - loader={ - - - - } - useWindow={false} - threshold={150} - > - {pointsVaultList.map((item, index) => ( - ( + + ))} + {isFetching && !isLoading && ( + + - ))} - + + )} + {totalItems > 0 && ( + + + + )} ); }; diff --git a/src/modules/pointsVault/components/PointsVaultRejectedList.tsx b/src/modules/pointsVault/components/PointsVaultRejectedList.tsx index 2b669e6ae1..5ea0e3f28b 100644 --- a/src/modules/pointsVault/components/PointsVaultRejectedList.tsx +++ b/src/modules/pointsVault/components/PointsVaultRejectedList.tsx @@ -1,16 +1,15 @@ -import { Box } from 'blocks'; +import { Box, Pagination } from 'blocks'; import { useQueryClient } from '@tanstack/react-query'; -import InfiniteScroll from 'react-infinite-scroller'; import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; import { PointsVaultListColumns } from './PointsVaultListColumns'; import { PointsVaultListItem } from './PointsVaultListItem'; import { - PointsVaultActivitiesResponse, pointsVaultApprovedUsers, - useGetPointsVaultRejectedUsers, + useGetPointsVaultRejectedUsersPaginated, usePointsVaultToken, } from 'queries'; import { LeaderBoardNullState } from 'modules/rewards/components/LeaderboardNullState'; +import { useEffect, useState } from 'react'; type PointsVaultRejectedListProps = { query: { @@ -21,26 +20,29 @@ type PointsVaultRejectedListProps = { const PointsVaultRejectedList = ({ query }: PointsVaultRejectedListProps) => { const token = usePointsVaultToken(); - const queryClient = useQueryClient(); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, refetch } = - useGetPointsVaultRejectedUsers({ - status: 'REJECTED', - token, - pageSize: 20, - twitter: query.twitter, - wallet: query.wallet, - activityTypeId: 'follow_push_on_twitter', - }); + const { data, isLoading, isError, refetch, isFetching } = useGetPointsVaultRejectedUsersPaginated({ + status: 'REJECTED', + token, + pageSize, + page: currentPage, + twitter: query.twitter, + wallet: query.wallet, + activityTypeId: 'follow_push_on_twitter', + }); - const hasMoreData = !isFetchingNextPage && hasNextPage; + // Reset to page 1 when search query changes + useEffect(() => { + setCurrentPage(1); + }, [query.twitter, query.wallet]); - const pointsVaultList = isLoading - ? Array(5).fill(0) - : data?.pages.flatMap((page: PointsVaultActivitiesResponse) => page.activities) || []; + const pointsVaultList = isLoading ? Array(5).fill(0) : data?.activities || []; + const totalItems = data?.total || 0; - if (!pointsVaultList.length) { + if (!pointsVaultList.length && !isLoading) { return ( { queryClient.invalidateQueries({ queryKey: [pointsVaultApprovedUsers] }); }; + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + return ( { flexDirection="column" > - fetchNextPage()} - hasMore={hasMoreData} - loader={ - - - - } - useWindow={false} - threshold={150} - > - {pointsVaultList.map((item, index) => ( - ( + + ))} + {isFetching && !isLoading && ( + + - ))} - + + )} + {totalItems > 0 && ( + + + + )} ); }; diff --git a/src/queries/hooks/pointsVault/index.ts b/src/queries/hooks/pointsVault/index.ts index 3e18ec3099..fe78c479d5 100644 --- a/src/queries/hooks/pointsVault/index.ts +++ b/src/queries/hooks/pointsVault/index.ts @@ -5,5 +5,8 @@ export * from './useGetUserTwitterDetails'; export * from './useGetUserTwitterDetails'; export * from './usePointsVaultToken'; export * from './useGetPointsVaultApprovedUsers'; +export * from './useGetPointsVaultApprovedUsersPaginated'; export * from './useGetPointsVaultPendingUsers'; +export * from './useGetPointsVaultPendingUsersPaginated'; export * from './useGetPointsVaultRejectedUsers'; +export * from './useGetPointsVaultRejectedUsersPaginated'; diff --git a/src/queries/hooks/pointsVault/useGetPointsVaultApprovedUsersPaginated.ts b/src/queries/hooks/pointsVault/useGetPointsVaultApprovedUsersPaginated.ts new file mode 100644 index 0000000000..cc7a5a8b17 --- /dev/null +++ b/src/queries/hooks/pointsVault/useGetPointsVaultApprovedUsersPaginated.ts @@ -0,0 +1,31 @@ +import { useQuery } from '@tanstack/react-query'; +import { pointsVaultApprovedUsers } from '../../queryKeys'; +import { PointsVaultGetUsersPayload } from 'queries/types'; +import { getPointsVaultUsers } from 'queries/services'; + +type UseGetPointsVaultApprovedUsersPaginatedProps = PointsVaultGetUsersPayload & { + page: number; +}; + +export const useGetPointsVaultApprovedUsersPaginated = ({ + status, + pageSize = 10, + page, + token, + twitter, + wallet, + activityTypeId, +}: UseGetPointsVaultApprovedUsersPaginatedProps) => { + return useQuery({ + queryKey: [pointsVaultApprovedUsers, `${twitter}-${wallet}`, page, pageSize], + queryFn: () => + getPointsVaultUsers({ status, page, pageSize, token, twitter, wallet, activityTypeId }), + enabled: !!token, + refetchInterval: false, + refetchIntervalInBackground: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryOnMount: false, + }); +}; diff --git a/src/queries/hooks/pointsVault/useGetPointsVaultPendingUsersPaginated.ts b/src/queries/hooks/pointsVault/useGetPointsVaultPendingUsersPaginated.ts new file mode 100644 index 0000000000..834bacafbb --- /dev/null +++ b/src/queries/hooks/pointsVault/useGetPointsVaultPendingUsersPaginated.ts @@ -0,0 +1,31 @@ +import { useQuery } from '@tanstack/react-query'; +import { pointsVaultPendingUsers } from '../../queryKeys'; +import { PointsVaultGetUsersPayload } from 'queries/types'; +import { getPointsVaultUsers } from 'queries/services'; + +type UseGetPointsVaultPendingUsersPaginatedProps = PointsVaultGetUsersPayload & { + page: number; +}; + +export const useGetPointsVaultPendingUsersPaginated = ({ + status, + pageSize = 10, + page, + token, + twitter, + wallet, + activityTypeId, +}: UseGetPointsVaultPendingUsersPaginatedProps) => { + return useQuery({ + queryKey: [pointsVaultPendingUsers, `${twitter}-${wallet}`, page, pageSize], + queryFn: () => + getPointsVaultUsers({ status, page, pageSize, token, twitter, wallet, activityTypeId }), + enabled: !!token, + refetchInterval: false, + refetchIntervalInBackground: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryOnMount: false, + }); +}; diff --git a/src/queries/hooks/pointsVault/useGetPointsVaultRejectedUsersPaginated.ts b/src/queries/hooks/pointsVault/useGetPointsVaultRejectedUsersPaginated.ts new file mode 100644 index 0000000000..db2665786d --- /dev/null +++ b/src/queries/hooks/pointsVault/useGetPointsVaultRejectedUsersPaginated.ts @@ -0,0 +1,31 @@ +import { useQuery } from '@tanstack/react-query'; +import { pointsVaultRejectedUsers } from '../../queryKeys'; +import { PointsVaultGetUsersPayload } from 'queries/types'; +import { getPointsVaultUsers } from 'queries/services'; + +type UseGetPointsVaultRejectedUsersPaginatedProps = PointsVaultGetUsersPayload & { + page: number; +}; + +export const useGetPointsVaultRejectedUsersPaginated = ({ + status, + pageSize = 10, + page, + token, + twitter, + wallet, + activityTypeId, +}: UseGetPointsVaultRejectedUsersPaginatedProps) => { + return useQuery({ + queryKey: [pointsVaultRejectedUsers, `${twitter}-${wallet}`, page, pageSize], + queryFn: () => + getPointsVaultUsers({ status, page, pageSize, token, twitter, wallet, activityTypeId }), + enabled: !!token, + refetchInterval: false, + refetchIntervalInBackground: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryOnMount: false, + }); +}; diff --git a/src/queries/hooks/rewards/index.ts b/src/queries/hooks/rewards/index.ts index 7609e40628..d4e0555958 100644 --- a/src/queries/hooks/rewards/index.ts +++ b/src/queries/hooks/rewards/index.ts @@ -3,6 +3,7 @@ export * from './useGetRewardsActivity'; export * from './useGetUserDiscordDetails'; export * from './claimRewardsActivity'; export * from './useGetUserRewardsDetails'; +export * from './useGetUserRewardsStats'; export * from './useCreateRewardsUser'; export * from './useGetRewardsLedearboard'; export * from './useGetRewardActivityStatus'; diff --git a/src/queries/hooks/rewards/useGetUserRewardsStats.ts b/src/queries/hooks/rewards/useGetUserRewardsStats.ts new file mode 100644 index 0000000000..51de738dd9 --- /dev/null +++ b/src/queries/hooks/rewards/useGetUserRewardsStats.ts @@ -0,0 +1,10 @@ +import { useQuery } from '@tanstack/react-query'; +import { userRewardsStats } from '../../queryKeys'; +import { getUserRewardsStats } from '../../services'; + +export const useGetUserRewardsStats = () => + useQuery({ + queryKey: [userRewardsStats], + queryFn: () => getUserRewardsStats(), + retry: false, + }); diff --git a/src/queries/models/rewards/getUserRewardsStatsModel.ts b/src/queries/models/rewards/getUserRewardsStatsModel.ts new file mode 100644 index 0000000000..8e4f2d66a9 --- /dev/null +++ b/src/queries/models/rewards/getUserRewardsStatsModel.ts @@ -0,0 +1,3 @@ +import { UserRewardsStatsResponse } from '../../types/rewards'; + +export const getUserRewardsStatsModel = (response: UserRewardsStatsResponse): UserRewardsStatsResponse => response; diff --git a/src/queries/models/rewards/index.ts b/src/queries/models/rewards/index.ts index f6d00ceca4..a4c1c6893a 100644 --- a/src/queries/models/rewards/index.ts +++ b/src/queries/models/rewards/index.ts @@ -3,6 +3,7 @@ export * from './getRewardsActivityModelCreator'; export * from './getUserDiscordDetailsModelCreator'; export * from './claimRewardsActivityModelCreator'; export * from './getUserRewardDetailsModel'; +export * from './getUserRewardsStatsModel'; export * from './createUserRewardsDetailsModel'; export * from './getRewardsLeaderboardModalCreator'; export * from './getRewardActivityStatusModel'; diff --git a/src/queries/queryKeys.ts b/src/queries/queryKeys.ts index 383fbe3a4f..5f61428837 100644 --- a/src/queries/queryKeys.ts +++ b/src/queries/queryKeys.ts @@ -49,6 +49,7 @@ export const userProfileInfo = 'userProfileInfo'; export const updateUserProfileDetails = 'updateUserProfileDetails'; export const userRewardsDetails = 'userRewardsDetails'; export const UserRewardsDetails = 'userRewardsDetails'; +export const userRewardsStats = 'userRewardsStats'; export const userSocialStatus = 'userSocialStatus'; export const userSubscription = 'userSubscription'; export const userTwitterDetails = 'userTwitterDetails'; diff --git a/src/queries/services/pointsVault/getPointsVaultUsers.ts b/src/queries/services/pointsVault/getPointsVaultUsers.ts index 9975a5ce2b..bf78bf5e3a 100644 --- a/src/queries/services/pointsVault/getPointsVaultUsers.ts +++ b/src/queries/services/pointsVault/getPointsVaultUsers.ts @@ -14,7 +14,7 @@ export const getPointsVaultUsers = ({ }: PointsVaultGetUsersPayload) => axios({ method: 'GET', - url: `${getRewardsBaseURL()}/activities/list`, + url: `${getRewardsBaseURL()}/v2/activities/list`, params: { status, page, diff --git a/src/queries/services/rewards/getUserRewardsStats.ts b/src/queries/services/rewards/getUserRewardsStats.ts new file mode 100644 index 0000000000..c61857518c --- /dev/null +++ b/src/queries/services/rewards/getUserRewardsStats.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +import { getUserRewardsStatsModel } from '../../models'; +import { getRewardsBaseURL } from '../../baseURL'; + +export const getUserRewardsStats = () => + axios({ + method: 'GET', + url: `${getRewardsBaseURL()}/v2/users/stats`, + }).then((response) => getUserRewardsStatsModel(response.data)); diff --git a/src/queries/services/rewards/index.ts b/src/queries/services/rewards/index.ts index 61f7d46820..27d72c7b4a 100644 --- a/src/queries/services/rewards/index.ts +++ b/src/queries/services/rewards/index.ts @@ -3,6 +3,7 @@ export * from './getRewardsActivity.ts'; export * from './getUserDiscordDetails'; export * from './claimRewardsActivity.ts'; export * from './getUserRewardsDetail.ts'; +export * from './getUserRewardsStats.ts'; export * from './createUserRewardsDetail.ts'; export * from './getRewardsLeaderboard'; export * from './getRewardActivityStatus.ts'; diff --git a/src/queries/types/pointsVault.ts b/src/queries/types/pointsVault.ts index 0a2fb9985e..73a7f5d922 100644 --- a/src/queries/types/pointsVault.ts +++ b/src/queries/types/pointsVault.ts @@ -26,6 +26,10 @@ export type PointsVaultActivity = { createdAt: string; // ISO 8601 date string updatedAt: string; // ISO 8601 date string userWallet: string; + primaryDiscordUserName: string; + secondaryDiscordUserName: string; + twitterUserName: string; + discordEmail: string; }; export type PointsVaultActivitiesResponse = { diff --git a/src/queries/types/rewards.ts b/src/queries/types/rewards.ts index cb206316aa..d57b98737f 100644 --- a/src/queries/types/rewards.ts +++ b/src/queries/types/rewards.ts @@ -183,6 +183,11 @@ export type UserRewardsDetailResponse = { usersInvited: number; }; +export type UserRewardsStatsResponse = { + totalUsers: number; + totalUsersWithEmail: number; +}; + export type createUserRewardsDetailsProps = { userWallet: string; pgpPublicKey: string;