From ef0a85e59d615431c30bf60bc6b08ac070d1827a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Mon, 22 Apr 2024 20:20:33 +0200 Subject: [PATCH 1/3] [#740] feat: add drep list pagination --- CHANGELOG.md | 1 + govtool/frontend/src/consts/queryKeys.ts | 2 +- .../src/hooks/forms/useDelegateTodRepForm.tsx | 4 +- .../src/hooks/queries/useGetDRepListQuery.ts | 82 ++++++++++++++----- govtool/frontend/src/models/api.ts | 12 +++ govtool/frontend/src/pages/DRepDetails.tsx | 10 ++- .../src/pages/DRepDirectoryContent.tsx | 28 +++++-- .../src/services/requests/getDRepList.ts | 33 ++++++-- .../src/services/requests/getProposals.ts | 4 +- govtool/frontend/yarn.lock | 36 ++++++-- 10 files changed, 158 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a26d74330..fee94d409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ changes. - Add frontend test workflow on github actions [Issue 500](https://github.com/IntersectMBO/govtool/issues/500) - Add type check & lint to github actions [Issue 512](https://github.com/IntersectMBO/govtool/issues/512) - Add eslint & prettier to frontend package [Issue 166](https://github.com/IntersectMBO/govtool/issues/166) +- Add DRep list pagination [Issue 740](https://github.com/IntersectMBO/govtool/issues/740) ### Fixed diff --git a/govtool/frontend/src/consts/queryKeys.ts b/govtool/frontend/src/consts/queryKeys.ts index 9a1e3c887..ccfdffe3f 100644 --- a/govtool/frontend/src/consts/queryKeys.ts +++ b/govtool/frontend/src/consts/queryKeys.ts @@ -1,7 +1,7 @@ export const QUERY_KEYS = { getAdaHolderCurrentDelegationKey: "getAdaHolderCurrentDelegationKey", getAdaHolderVotingPowerKey: "getAdaHolderVotingPowerKey", - useGetDRepListKey: "useGetDRepListKey", + useGetDRepListInfiniteKey: "useGetDRepListInfiniteKey", useGetDRepVotesKey: "useGetDRepVotesKey", useGetDRepVotingPowerKey: "useGetDRepVotingPowerKey", useGetProposalKey: "useGetProposalKey", diff --git a/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx b/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx index 12794fd79..962e14556 100644 --- a/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx +++ b/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx @@ -4,7 +4,7 @@ import { useForm, useWatch } from "react-hook-form"; import { PATHS } from "@consts"; import { useCardano, useModal } from "@context"; -import { useGetDRepListQuery, useTranslation } from "@hooks"; +import { useGetDRepListInfiniteQuery, useTranslation } from "@hooks"; import { formHexToBech32 } from "@utils"; export interface DelegateTodrepFormValues { @@ -13,7 +13,7 @@ export interface DelegateTodrepFormValues { export const useDelegateTodRepForm = () => { const { buildSignSubmitConwayCertTx, buildVoteDelegationCert } = useCardano(); - const { data: drepList } = useGetDRepListQuery(); + const { data: drepList } = useGetDRepListInfiniteQuery(); const { openModal, closeModal, modal } = useModal(); const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts index 47161bcc4..4ae33f960 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts @@ -1,35 +1,73 @@ -import { UseQueryOptions, useQuery } from "react-query"; +import { UseInfiniteQueryOptions, useInfiniteQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; -import { GetDRepListParams, getDRepList } from "@services"; -import { DRepData } from "@/models"; +import { GetDRepListArguments, getDRepList } from "@services"; +import { InfinityDRepData } from "@/models"; -export const useGetDRepListQuery = ( - params?: GetDRepListParams, - options?: UseQueryOptions +export const useGetDRepListInfiniteQuery = ( + { + filters = [], + pageSize = 10, + searchPhrase, + sorting, + status, + }: GetDRepListArguments, + options?: UseInfiniteQueryOptions, ) => { - const { search, sort, status } = params || {}; const { pendingTransaction } = useCardano(); - const { data, isLoading, isPreviousData } = useQuery({ - queryKey: [ - QUERY_KEYS.useGetDRepListKey, - (pendingTransaction.registerAsSoleVoter || + const { + data, + isLoading, + fetchNextPage, + hasNextPage, + isFetching, + isFetchingNextPage, + isPreviousData, + } = useInfiniteQuery( + [ + QUERY_KEYS.useGetDRepListInfiniteKey, + ( + pendingTransaction.registerAsSoleVoter || pendingTransaction.registerAsDrep || pendingTransaction.retireAsSoleVoter || - pendingTransaction.retireAsDrep)?.transactionHash, - search, - sort, + pendingTransaction.retireAsDrep + )?.transactionHash, + filters, + searchPhrase, + sorting, status, ], - queryFn: () => getDRepList({ - ...(search && { search }), - ...(sort && { sort }), - ...(status && { status }), - }), - ...options - }); + async ({ pageParam = 0 }) => + getDRepList({ + page: pageParam, + pageSize, + filters, + searchPhrase, + sorting, + status, + }), + { + getNextPageParam: (lastPage) => { + if (lastPage.elements.length === 0) { + return undefined; + } - return { data, isLoading, isPreviousData }; + return lastPage.page + 1; + }, + enabled: options?.enabled, + keepPreviousData: options?.keepPreviousData, + }, + ); + + return { + dRepListFetchNextPage: fetchNextPage, + dRepListHasNextPage: hasNextPage, + isDRepListFetching: isFetching, + isDRepListFetchingNextPage: isFetchingNextPage, + isDRepListLoading: isLoading, + dRepData: data?.pages.flatMap((page) => page.elements), + isPreviousData, + }; }; diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 5b72cff60..ba1b2eb28 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -18,6 +18,12 @@ export enum DRepStatus { Retired = "Retired", } +export enum DRepListSort { + VotingPower = "VotingPower", + RegistrationDate = "RegistrationDate", + Status = "Status", +} + export interface DRepData { drepId: string; view: string; @@ -28,6 +34,12 @@ export interface DRepData { status: DRepStatus; type: "DRep" | "SoleVoter"; } +export type InfinityDRepData = { + elements: DRepData[]; + page: number; + pageSize: number; + total: number; +}; export type Vote = "yes" | "no" | "abstain"; diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx index a5b9f7f0e..d8565ebb3 100644 --- a/govtool/frontend/src/pages/DRepDetails.tsx +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -7,7 +7,7 @@ import { ICONS, PATHS } from "@consts"; import { useCardano, useModal } from "@context"; import { useDelegateTodRep, - useGetDRepListQuery, + useGetDRepListInfiniteQuery, useScreenDimension, useTranslation, } from "@hooks"; @@ -36,10 +36,12 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { delegate, isDelegating } = useDelegateTodRep(); - const { data, isLoading } = useGetDRepListQuery({ search: dRepParam }); - const dRep = data?.[0]; + const { dRepData, isDRepListLoading } = useGetDRepListInfiniteQuery({ + searchPhrase: dRepParam, + }); + const dRep = dRepData?.[0]; - if (data === undefined || isLoading) + if (dRep === undefined || isDRepListLoading) return ( = ({ const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const inProgressDelegation = pendingTransaction.delegate?.resourceId; - const { data: myDRepList } = useGetDRepListQuery( + const { + dRepData: myDRepList, + dRepListHasNextPage, + dRepListFetchNextPage, + } = useGetDRepListInfiniteQuery( { - search: currentDelegation?.dRepView?.startsWith("drep") + searchPhrase: currentDelegation?.dRepView?.startsWith("drep") ? currentDelegation.dRepView : formHexToBech32(currentDelegation?.dRepHash ?? ""), }, { enabled: !!inProgressDelegation || !!currentDelegation }, ); const myDrep = myDRepList?.[0]; - const { data: dRepList, isPreviousData } = useGetDRepListQuery( + const { dRepData: dRepList, isPreviousData } = useGetDRepListInfiniteQuery( { - search: debouncedSearchText, - sort: chosenSorting, - status: chosenFilters, + searchPhrase: debouncedSearchText, + sorting: chosenSorting as DRepListSort, + status: chosenFilters as DRepStatus[], }, { keepPreviousData: true, @@ -186,6 +191,13 @@ export const DRepDirectoryContent: FC = ({ })} + {dRepListHasNextPage && ( + + + + )} ); }; diff --git a/govtool/frontend/src/services/requests/getDRepList.ts b/govtool/frontend/src/services/requests/getDRepList.ts index 43430bf68..7d18ee43a 100644 --- a/govtool/frontend/src/services/requests/getDRepList.ts +++ b/govtool/frontend/src/services/requests/getDRepList.ts @@ -1,13 +1,32 @@ -import type { DRepData } from "@models"; +import type { InfinityDRepData, DRepStatus, DRepListSort } from "@models"; import { API } from "../API"; -export type GetDRepListParams = { - search?: string; - sort?: string; - status?: string[]; +export type GetDRepListArguments = { + filters?: string[]; + page?: number; + pageSize?: number; + sorting?: DRepListSort; + status?: DRepStatus[]; + searchPhrase?: string; }; -export const getDRepList = async (params: GetDRepListParams) => { - const response = await API.get("/drep/list", { params }); +export const getDRepList = async ({ + sorting, + filters = [], + page = 0, + pageSize = 10, + searchPhrase = "", + status = [], +}: GetDRepListArguments): Promise => { + const response = await API.get("/drep/list", { + params: { + page, + pageSize, + ...(searchPhrase && { search: searchPhrase }), + ...(filters.length && { type: filters }), + ...(sorting && { sort: sorting }), + ...(status.length && { status }), + }, + }); return response.data; }; diff --git a/govtool/frontend/src/services/requests/getProposals.ts b/govtool/frontend/src/services/requests/getProposals.ts index ed8fa0cfb..e5cd14b1f 100644 --- a/govtool/frontend/src/services/requests/getProposals.ts +++ b/govtool/frontend/src/services/requests/getProposals.ts @@ -18,7 +18,7 @@ export const getProposals = async ({ pageSize = 7, searchPhrase = "", sorting = "", -}: GetProposalsArguments) => { +}: GetProposalsArguments): Promise => { const response = await API.get("/proposal/list", { params: { page, @@ -29,5 +29,5 @@ export const getProposals = async ({ ...(dRepID && { drepId: dRepID }), }, }); - return response.data as InfinityProposals; + return response.data; }; diff --git a/govtool/frontend/yarn.lock b/govtool/frontend/yarn.lock index bc05cc41f..d32bcde1a 100644 --- a/govtool/frontend/yarn.lock +++ b/govtool/frontend/yarn.lock @@ -7415,11 +7415,6 @@ isobject@^3.0.1: resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -istanbul-badges-readme@^1.8.5: - version "1.8.5" - resolved "https://registry.npmjs.org/istanbul-badges-readme/-/istanbul-badges-readme-1.8.5.tgz" - integrity sha512-VEh90ofuufPZIbLeDF2g14wpe0sebhirG0xHooYKpNPYOkGXm6y+HJFotQqIzCg0NmbCnlKnOPL1B2oxG7/piA== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0, istanbul-lib-coverage@^3.2.2: version "3.2.2" resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz" @@ -10358,7 +10353,16 @@ string-length@^5.0.1: char-regex "^2.0.0" strip-ansi "^7.0.1" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10436,7 +10440,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11349,7 +11360,7 @@ wordwrap@^1.0.0: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -11367,6 +11378,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" From 1797957bd995b6f502499131402543340069d3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Mon, 22 Apr 2024 20:26:20 +0200 Subject: [PATCH 2/3] [#740] fix: fix type check error on useDelegateTodRepForm hook --- govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx b/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx index 962e14556..7de68755c 100644 --- a/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx +++ b/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx @@ -13,7 +13,9 @@ export interface DelegateTodrepFormValues { export const useDelegateTodRepForm = () => { const { buildSignSubmitConwayCertTx, buildVoteDelegationCert } = useCardano(); - const { data: drepList } = useGetDRepListInfiniteQuery(); + const { dRepData: drepList } = useGetDRepListInfiniteQuery({ + page: 0, + }); const { openModal, closeModal, modal } = useModal(); const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); From 6df0295d96d1162c6080ad5352e70dc0f6640dfa Mon Sep 17 00:00:00 2001 From: Jan Jaroszczak Date: Tue, 23 Apr 2024 14:52:53 +0200 Subject: [PATCH 3/3] fix: hasNextPage and fetchNextPage fix --- .../frontend/src/pages/DRepDirectoryContent.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index 480fc09f8..ff18817bd 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -48,11 +48,7 @@ export const DRepDirectoryContent: FC = ({ const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const inProgressDelegation = pendingTransaction.delegate?.resourceId; - const { - dRepData: myDRepList, - dRepListHasNextPage, - dRepListFetchNextPage, - } = useGetDRepListInfiniteQuery( + const { dRepData: myDRepList } = useGetDRepListInfiniteQuery( { searchPhrase: currentDelegation?.dRepView?.startsWith("drep") ? currentDelegation.dRepView @@ -61,7 +57,13 @@ export const DRepDirectoryContent: FC = ({ { enabled: !!inProgressDelegation || !!currentDelegation }, ); const myDrep = myDRepList?.[0]; - const { dRepData: dRepList, isPreviousData } = useGetDRepListInfiniteQuery( + + const { + dRepData: dRepList, + isPreviousData, + dRepListHasNextPage, + dRepListFetchNextPage, + } = useGetDRepListInfiniteQuery( { searchPhrase: debouncedSearchText, sorting: chosenSorting as DRepListSort,