diff --git a/.changeset/giant-cougars-sort.md b/.changeset/giant-cougars-sort.md new file mode 100644 index 000000000..98055f8a3 --- /dev/null +++ b/.changeset/giant-cougars-sort.md @@ -0,0 +1,5 @@ +--- +"@carrot-kpi/host-frontend": minor +--- + +Refactor the all campaigns page to fetch and resolve all the kpi tokens diff --git a/.changeset/quiet-sheep-decide.md b/.changeset/quiet-sheep-decide.md new file mode 100644 index 000000000..ac009aa14 --- /dev/null +++ b/.changeset/quiet-sheep-decide.md @@ -0,0 +1,5 @@ +--- +"@carrot-kpi/react": minor +--- + +Add new useResolvedKPITokens hook to fetch and resolve the kpi tokens diff --git a/packages/frontend/src/pages/campaigns/grid/index.tsx b/packages/frontend/src/pages/campaigns/grid/index.tsx index 0bc3b60de..9b6b22424 100644 --- a/packages/frontend/src/pages/campaigns/grid/index.tsx +++ b/packages/frontend/src/pages/campaigns/grid/index.tsx @@ -1,10 +1,10 @@ -import { usePagination } from "@carrot-kpi/react"; import type { KPIToken, ResolvedKPIToken } from "@carrot-kpi/sdk"; import React, { useCallback, useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { KPITokenCard } from "../../../components/ui/kpi-token-card"; import { Empty } from "../../../components/ui/empty"; import { Pagination } from "../../../components/pagination"; +import { usePagination } from "@carrot-kpi/react"; const placeholder = new Array(8) .fill(null) @@ -14,15 +14,9 @@ interface GridProps { loading?: boolean; kpiTokensReady?: boolean; items: (KPIToken | ResolvedKPIToken)[]; - onResolved: (resolved: ResolvedKPIToken) => void; } -export const Grid = ({ - loading, - kpiTokensReady, - items, - onResolved, -}: GridProps) => { +export const Grid = ({ loading, kpiTokensReady, items }: GridProps) => { const [searchParams, setSearchParams] = useSearchParams(); const [page, setPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(4); @@ -45,15 +39,14 @@ export const Grid = ({ resizeObserver.observe(document.body); return () => { - // eslint-disable-next-line react-hooks/exhaustive-deps resizeObserver.unobserve(document.body); }; }); const { data: paginatedItems, totalPages } = usePagination({ data: items, - currentPage: page, - totalItems: itemsPerPage, + page, + size: itemsPerPage, }); const handlePageChange = useCallback( @@ -73,25 +66,24 @@ export const Grid = ({ return (
-
- {loading || !kpiTokensReady ? ( - placeholder - ) : paginatedItems.length > 0 ? ( - paginatedItems.map( - (kpiToken: KPIToken | ResolvedKPIToken) => { - return ( - - ); - }, - ) - ) : ( - - )} -
+ {!loading && kpiTokensReady && paginatedItems.length === 0 ? ( + + ) : ( +
+ {loading || !kpiTokensReady + ? placeholder + : paginatedItems.map( + (kpiToken: KPIToken | ResolvedKPIToken) => { + return ( + + ); + }, + )} +
+ )} { const { t } = useTranslation(); const location = useLocation(); const [, setSearchParams] = useSearchParams(); - const { loading, kpiTokens: response } = useKPITokens(); + const { loading, resolvedKPITokens } = useResolvedKPITokens(); const [kpiTokens, setKpiTokens] = useState<(KPIToken | ResolvedKPIToken)[]>( [], @@ -49,9 +49,9 @@ export const Campaigns = () => { useEffect(() => { if (loading || kpiTokens.length > 0) return; - setKpiTokens(Object.values(response)); + setKpiTokens(Object.values(resolvedKPITokens)); setKpiTokensReady(true); - }, [response, kpiTokens, loading]); + }, [resolvedKPITokens, kpiTokens, loading]); useEffect(() => { const url = new URLSearchParams(location.search); @@ -78,35 +78,6 @@ export const Campaigns = () => { ); }, [kpiTokens, state.value, sort.value, debouncedSearchQuery]); - const handleOnResolvedKPIToken = useCallback( - (resolved: ResolvedKPIToken) => { - if ( - ( - kpiTokens.find( - (kpiToken) => kpiToken.address === resolved.address, - ) as ResolvedKPIToken - ).specification - ) - return; - - const updatedKPITokens = kpiTokens.map((kpiToken) => { - if (kpiToken.address === resolved.address) { - return { - ...kpiToken, - specification: resolved.specification, - expired: resolved.expired, - oracles: resolved.oracles, - template: resolved.template, - }; - } else { - return kpiToken; - } - }); - setKpiTokens(updatedKPITokens); - }, - [kpiTokens], - ); - const updateURLParams = useCallback( (key: string, value: string) => { const searchParams = new URLSearchParams(location.search); @@ -145,7 +116,7 @@ export const Campaigns = () => { return ( -
+
@@ -180,7 +151,6 @@ export const Campaigns = () => { loading={loading} kpiTokensReady={kpiTokensReady} items={sortedAndfilteredKPITokens} - onResolved={handleOnResolvedKPIToken} />
diff --git a/packages/react/package.json b/packages/react/package.json index cbe22dbed..a7637c1b4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -124,6 +124,11 @@ "import": "./dist/es/hooks/useResolvedKPIToken.mjs", "default": "./dist/cjs/hooks/useResolvedKPIToken.cjs" }, + "./useResolvedKPITokens": { + "types": "./dist/types/hooks/useResolvedKPITokens.d.ts", + "import": "./dist/es/hooks/useResolvedKPITokens.mjs", + "default": "./dist/cjs/hooks/useResolvedKPITokens.cjs" + }, "./useResolvedTemplate": { "types": "./dist/types/hooks/useResolvedTemplate.d.ts", "import": "./dist/es/hooks/useResolvedTemplate.mjs", @@ -273,6 +278,9 @@ "useResolvedKPIToken": [ "./dist/types/hooks/useResolvedKPIToken.d.ts" ], + "useResolvedKPITokens": [ + "./dist/types/hooks/useResolvedKPITokens.d.ts" + ], "useResolvedTemplate": [ "./dist/types/hooks/useResolvedTemplate.d.ts" ], diff --git a/packages/react/src/hooks/usePagination.ts b/packages/react/src/hooks/usePagination.ts index 8b525eac3..7377d5d8a 100644 --- a/packages/react/src/hooks/usePagination.ts +++ b/packages/react/src/hooks/usePagination.ts @@ -1,19 +1,20 @@ -const ITEMS_PER_PAGE = 18; +const ITEMS_PER_PAGE = 18 as const; interface PaginationParams { data: T[]; - currentPage: number; - totalItems?: number; + page: number; + size?: number; } export const usePagination = (params: PaginationParams) => { - const { data, currentPage, totalItems } = params; - const itemsPerPage = totalItems ? totalItems : ITEMS_PER_PAGE; - const startIndex = (currentPage - 1) * itemsPerPage; - const endIndex = startIndex + itemsPerPage; - const slicedData = - data.length > itemsPerPage ? data.slice(startIndex, endIndex) : data; - const totalPages = Math.ceil(data.length / itemsPerPage); + const { data, page, size } = params; - return { data: slicedData, totalPages }; + const pageSize = size ? size : ITEMS_PER_PAGE; + const startIndex = (page - 1) * pageSize; + const endIndex = startIndex + pageSize; + const pageData = + data.length > pageSize ? data.slice(startIndex, endIndex) : data; + const totalPages = Math.ceil(data.length / pageSize); + + return { data: pageData, totalPages }; }; diff --git a/packages/react/src/hooks/useResolvedKPITokens.ts b/packages/react/src/hooks/useResolvedKPITokens.ts new file mode 100644 index 000000000..529567b4e --- /dev/null +++ b/packages/react/src/hooks/useResolvedKPITokens.ts @@ -0,0 +1,77 @@ +import { Fetcher, ResolvedKPIToken } from "@carrot-kpi/sdk"; +import { type Address, usePublicClient } from "wagmi"; +import { useEffect, useState } from "react"; +import { useIPFSGatewayURL } from "./useIPFSGatewayURL"; +import { usePreferDecentralization } from "./usePreferDecentralization"; + +interface ResolvedKPITokensParams { + blacklisted?: Address[]; +} + +export function useResolvedKPITokens(params?: ResolvedKPITokensParams): { + loading: boolean; + resolvedKPITokens: ResolvedKPIToken[]; +} { + const publicClient = usePublicClient(); + const preferDecentralization = usePreferDecentralization(); + const ipfsGatewayURL = useIPFSGatewayURL(); + const [resolvedKPITokens, setResolvedKPITokens] = useState< + ResolvedKPIToken[] + >([]); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + async function fetchData(): Promise { + if (!cancelled) setLoading(true); + + try { + const kpiTokens = await Fetcher.fetchKPITokens({ + publicClient, + preferDecentralization, + blacklisted: params?.blacklisted, + }); + + await Promise.allSettled( + Object.values(kpiTokens).map(async (kpiToken) => { + try { + const resolved = ( + await Fetcher.resolveKPITokens({ + ipfsGatewayURL, + kpiTokens: [kpiToken], + }) + )[kpiToken.address]; + if (!resolved) return; + if (!cancelled) + setResolvedKPITokens((previousState) => [ + ...previousState, + resolved, + ]); + } catch (error) { + console.error( + `error resolving kpi token at address ${kpiToken.address}`, + error, + ); + } + }), + ); + } catch (error) { + console.error("error fetching resolved kpi tokens", error); + } finally { + if (!cancelled) setLoading(false); + } + } + void fetchData(); + return () => { + cancelled = true; + }; + }, [ + ipfsGatewayURL, + publicClient, + preferDecentralization, + params?.blacklisted, + ]); + + return { loading, resolvedKPITokens }; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 669fdba28..21a982ec2 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -20,6 +20,7 @@ export * from "./hooks/useOracleTemplates"; export * from "./hooks/usePagination"; export * from "./hooks/usePreferDecentralization"; export * from "./hooks/useResolvedKPIToken"; +export * from "./hooks/useResolvedKPITokens"; export * from "./hooks/useResolvedTemplate"; export * from "./hooks/useResolvedTemplates"; export * from "./hooks/useSetDevMode";