Skip to content

Commit

Permalink
feat(phase-2): finality providers (#239)
Browse files Browse the repository at this point in the history
* feat(phase-2): update FP component

* feat(pjase-2): use server side filtering for FP
  • Loading branch information
totraev authored Oct 28, 2024
1 parent 7864667 commit 91fb335
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 113 deletions.
22 changes: 19 additions & 3 deletions src/app/api/getFinalityProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface FinalityProvidersAPIResponse {

interface FinalityProviderAPI {
description: DescriptionAPI;
state: "active" | "standby";
commission: string;
btc_pk: string;
active_tvl: number;
Expand All @@ -35,14 +36,28 @@ interface DescriptionAPI {
details: string;
}

export const getFinalityProviders = async (
key: string,
): Promise<PaginatedFinalityProviders> => {
export const getFinalityProviders = async ({
key,
pk,
sortBy,
order,
name,
}: {
key: string;
name?: string;
sortBy?: string;
order?: "asc" | "desc";
pk?: string;
}): Promise<PaginatedFinalityProviders> => {
// const limit = 100;
// const reverse = false;

const params = {
pagination_key: encode(key),
finality_provider_pk: pk,
sort_by: sortBy,
order,
name,
// "pagination_reverse": reverse,
// "pagination_limit": limit,
};
Expand Down Expand Up @@ -70,6 +85,7 @@ export const getFinalityProviders = async (
securityContact: fp.description.security_contact,
details: fp.description.details,
},
state: fp.state,
commission: fp.commission,
btcPk: fp.btc_pk,
activeTVLSat: fp.active_tvl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { maxDecimals } from "@/utils/maxDecimals";
interface FinalityProviderProps {
moniker: string;
pkHex: string;
state: "active" | "standby";
stakeSat: number;
commission: string;
onClick: () => void;
Expand All @@ -22,6 +23,7 @@ interface FinalityProviderProps {

export const FinalityProvider: React.FC<FinalityProviderProps> = ({
moniker,
state = "active",
pkHex,
stakeSat,
commission,
Expand Down Expand Up @@ -127,6 +129,9 @@ export const FinalityProvider: React.FC<FinalityProviderProps> = ({
className="tooltip-wrap"
/>
</div>
<div className="flex justify-start gap-1 capitalize lg:justify-end">
<span className="lg:hidden">Status:</span> {state}
</div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { useDebounce } from "@uidotdev/usehooks";
import React, { useEffect, useState } from "react";
import React from "react";
import { FiSearch } from "react-icons/fi";

interface FinalityProviderSearchProps {
searchValue: string;
onSearch: (searchTerm: string) => void;
}

export const FinalityProviderSearch: React.FC<FinalityProviderSearchProps> = ({
searchValue,
onSearch,
}) => {
const [searchTerm, setSearchTerm] = useState("");
const debouncedSearchTerm = useDebounce(searchTerm, 300);

useEffect(() => {
onSearch(debouncedSearchTerm);
}, [debouncedSearchTerm, onSearch]);

const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
onSearch(e.target.value);
};

return (
Expand All @@ -29,7 +23,7 @@ export const FinalityProviderSearch: React.FC<FinalityProviderSearchProps> = ({
<input
type="text"
placeholder="Search by Name or Public Key"
value={searchTerm}
value={searchValue}
onChange={handleSearch}
className="w-full pl-10 pr-4 py-2 text-sm bg-transparent border-b border-gray-300 focus:outline-none focus:border-primary"
/>
Expand Down
129 changes: 41 additions & 88 deletions src/app/components/Staking/FinalityProviders/FinalityProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,44 @@
import { useInfiniteQuery } from "@tanstack/react-query";
import { useEffect } from "react";
import InfiniteScroll from "react-infinite-scroll-component";

import {
PaginatedFinalityProviders,
getFinalityProviders,
} from "@/app/api/getFinalityProviders";
import {
LoadingTableList,
LoadingView,
} from "@/app/components/Loading/Loading";
import { useError } from "@/app/context/Error/ErrorContext";
import { useFinalityProvidersData } from "@/app/hooks/finalityProviders/useFinalityProvidersData";
import { ErrorState } from "@/app/types/errors";
import { FinalityProvidersProps } from "@/app/types/finalityProviders";
import { useFinalityProviderService } from "@/app/hooks/services/useFinalityProviderService";
import type { FinalityProvider as FinalityProviderInterface } from "@/app/types/finalityProviders";

import { FinalityProvider } from "./FinalityProvider";
import { FinalityProviderSearch } from "./FinalityProviderSearch";

export interface FinalityProvidersProps {
onFinalityProvidersLoad: (data: FinalityProviderInterface[]) => void;
selectedFinalityProvider: FinalityProviderInterface | undefined;
onFinalityProviderChange: (btcPkHex: string) => void;
}

export const FinalityProviders: React.FC<FinalityProvidersProps> = ({
selectedFinalityProvider,
onFinalityProviderChange,
onFinalityProvidersLoad,
}) => {
const { isErrorOpen, showError, handleError } = useError();
const {
data: fps,
fetchNextPage: finalityProvidersFetchNext,
hasNextPage: finalityProvidersHasNext,
isFetchingNextPage: finalityProvidersIsFetchingMore,
error: finalityProvidersError,
isError: hasFinalityProvidersError,
refetch: refetchFinalityProvidersData,
isRefetchError: isRefetchFinalityProvidersError,
} = useInfiniteQuery({
queryKey: ["finality providers"],
queryFn: ({ pageParam = "" }) => getFinalityProviders(pageParam),
getNextPageParam: (lastPage) =>
lastPage?.pagination?.next_key !== ""
? lastPage?.pagination?.next_key
: null,
initialPageParam: "",
refetchInterval: 60000, // 1 minute
select: (data) => {
const flattenedData = data.pages.reduce<PaginatedFinalityProviders>(
(acc, page) => {
acc.finalityProviders.push(...page.finalityProviders);
acc.pagination = page.pagination;
return acc;
},
{ finalityProviders: [], pagination: { next_key: "" } },
);
return flattenedData;
},
retry: (failureCount) => {
return !isErrorOpen && failureCount <= 3;
},
});

useEffect(() => {
fps?.finalityProviders && onFinalityProvidersLoad(fps.finalityProviders);
}, [fps, onFinalityProvidersLoad]);
isLoading,
finalityProviders,
searchValue,
hasNextPage,
fetchNextPage,
handleSearch,
handleSort,
} = useFinalityProviderService();

useEffect(() => {
if (
finalityProvidersHasNext &&
finalityProvidersFetchNext &&
!finalityProvidersIsFetchingMore
) {
finalityProvidersFetchNext();
if (finalityProviders) {
onFinalityProvidersLoad(finalityProviders);
}
}, [
finalityProvidersHasNext,
finalityProvidersFetchNext,
finalityProvidersIsFetchingMore,
]);

useEffect(() => {
handleError({
error: finalityProvidersError,
hasError: hasFinalityProvidersError,
errorState: ErrorState.SERVER_ERROR,
refetchFunction: refetchFinalityProvidersData,
});
}, [
hasFinalityProvidersError,
isRefetchFinalityProvidersError,
finalityProvidersError,
refetchFinalityProvidersData,
showError,
handleError,
]);

const { handleSearch, filteredProviders } = useFinalityProvidersData(
fps?.finalityProviders,
);
}, [finalityProviders, onFinalityProvidersLoad]);

if (!fps?.finalityProviders?.length) {
if (!finalityProviders?.length) {
return <LoadingView />;
}

Expand All @@ -106,29 +48,40 @@ export const FinalityProviders: React.FC<FinalityProvidersProps> = ({
<strong>Step-1:</strong> Select a finality provider
</p>
<div className="flex gap-3">
<FinalityProviderSearch onSearch={handleSearch} />
<FinalityProviderSearch
searchValue={searchValue}
onSearch={handleSearch}
/>
</div>
<div className="hidden gap-2 px-4 lg:grid lg:grid-cols-stakingFinalityProvidersDesktop">
<p>Finality Provider</p>
<p>BTC PK</p>
<p>Total Delegation</p>
<p>Commission</p>
<p className="cursor-pointer" onClick={() => handleSort("name")}>
Finality Provider
</p>
<p className="cursor-default">BTC PK</p>
<p className="cursor-pointer" onClick={() => handleSort("active_tvl")}>
Total Delegation
</p>
<p className="cursor-pointer" onClick={() => handleSort("commission")}>
Commission
</p>
<p className="cursor-default text-right">Status</p>
</div>
<div
id="finality-providers"
className="no-scrollbar max-h-[21rem] overflow-y-auto"
>
<InfiniteScroll
className="flex flex-col gap-4"
dataLength={filteredProviders?.length || 0}
next={finalityProvidersFetchNext}
hasMore={finalityProvidersHasNext}
loader={finalityProvidersIsFetchingMore ? <LoadingTableList /> : null}
dataLength={finalityProviders?.length || 0}
next={fetchNextPage}
hasMore={hasNextPage}
loader={isLoading ? <LoadingTableList /> : null}
scrollableTarget="finality-providers"
>
{filteredProviders?.map((fp) => (
{finalityProviders?.map((fp) => (
<FinalityProvider
key={fp.btcPk}
state={fp.state}
moniker={fp.description?.moniker}
website={fp.description?.website}
pkHex={fp.btcPk}
Expand Down
61 changes: 61 additions & 0 deletions src/app/hooks/api/useFinalityProviders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useInfiniteQuery } from "@tanstack/react-query";
import { useEffect } from "react";

import {
type PaginatedFinalityProviders,
getFinalityProviders,
} from "@/app/api/getFinalityProviders";
import { ONE_MINUTE } from "@/app/constants";
import { useError } from "@/app/context/Error/ErrorContext";
import { ErrorState } from "@/app/types/errors";

const FINALITY_PROVIDERS_KEY = "GET_FINALITY_PROVIDERS_KEY";

interface Params {
pk?: string;
name?: string;
sortBy?: string;
order?: "asc" | "desc";
}

export function useFinalityProviders({ pk, sortBy, order, name }: Params = {}) {
const { isErrorOpen, handleError } = useError();

const query = useInfiniteQuery({
queryKey: [FINALITY_PROVIDERS_KEY, pk, name, sortBy, order],
queryFn: ({ pageParam = "" }) =>
getFinalityProviders({ key: pageParam, pk, sortBy, order, name }),
getNextPageParam: (lastPage) =>
lastPage?.pagination?.next_key !== ""
? lastPage?.pagination?.next_key
: null,
initialPageParam: "",
refetchInterval: ONE_MINUTE,
placeholderData: (prev) => prev,
select: (data) => {
const flattenedData = data.pages.reduce<PaginatedFinalityProviders>(
(acc, page) => {
acc.finalityProviders.push(...page.finalityProviders);
acc.pagination = page.pagination;
return acc;
},
{ finalityProviders: [], pagination: { next_key: "" } },
);
return flattenedData;
},
retry: (failureCount) => {
return !isErrorOpen && failureCount <= 3;
},
});

useEffect(() => {
handleError({
error: query.error,
hasError: query.isError,
errorState: ErrorState.SERVER_ERROR,
refetchFunction: query.refetch,
});
}, [query.isError, query.error, query.refetch, handleError]);

return query;
}
Loading

0 comments on commit 91fb335

Please sign in to comment.