+
+
{renderStakingForm()}
diff --git a/src/app/hooks/client/api/useFinalityProvidersV2.ts b/src/app/hooks/client/api/useFinalityProviders.ts
similarity index 100%
rename from src/app/hooks/client/api/useFinalityProvidersV2.ts
rename to src/app/hooks/client/api/useFinalityProviders.ts
diff --git a/src/app/hooks/services/useFinalityProviderService.ts b/src/app/hooks/services/useFinalityProviderService.ts
index 5c30021a..d30f8321 100644
--- a/src/app/hooks/services/useFinalityProviderService.ts
+++ b/src/app/hooks/services/useFinalityProviderService.ts
@@ -1,7 +1,10 @@
import { useDebounce } from "@uidotdev/usehooks";
-import { useCallback, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
-import { useFinalityProviders } from "@/app/hooks/client/api/useFinalityProvidersV2";
+import { useError } from "@/app/context/Error/ErrorContext";
+import { useFinalityProviders } from "@/app/hooks/client/api/useFinalityProviders";
+import { ErrorState } from "@/app/types/errors";
+import { FinalityProvider } from "@/app/types/finalityProviders";
interface SortState {
field?: string;
@@ -14,18 +17,36 @@ const SORT_DIRECTIONS = {
asc: undefined,
} as const;
-export function useFinalityProviderService() {
+export function useFinalityProviderServiceState() {
const [searchValue, setSearchValue] = useState("");
+ const [filterValue, setFilterValue] = useState("active");
const [sortState, setSortState] = useState({});
- const name = useDebounce(searchValue, 300);
+ const [selectedFinalityProvider, setSelectedFinalityProvider] =
+ useState(null);
+ const { showError, captureError } = useError();
- const { data, hasNextPage, isFetchingNextPage, fetchNextPage } =
+ const debouncedSearch = useDebounce(searchValue, 300);
+
+ const { data, hasNextPage, fetchNextPage, isFetching, isError, error } =
useFinalityProviders({
sortBy: sortState.field,
order: sortState.direction,
- name,
+ name: debouncedSearch,
});
+ useEffect(() => {
+ if (isError && error) {
+ showError({
+ error: {
+ message: error.message,
+ errorState: ErrorState.SERVER_ERROR,
+ },
+ retryAction: fetchNextPage,
+ });
+ captureError(error);
+ }
+ }, [isError, error, showError, captureError, fetchNextPage]);
+
const handleSearch = useCallback((searchTerm: string) => {
setSearchValue(searchTerm);
}, []);
@@ -44,9 +65,42 @@ export function useFinalityProviderService() {
);
}, []);
+ const handleFilter = useCallback((value: string | number) => {
+ setFilterValue(value);
+ }, []);
+
+ const handleRowSelect = useCallback((row: FinalityProvider) => {
+ setSelectedFinalityProvider(row);
+ }, []);
+
+ const filteredFinalityProviders = useMemo(() => {
+ if (!data?.finalityProviders) return [];
+
+ return data.finalityProviders.filter((fp: FinalityProvider) => {
+ const statusMatch =
+ filterValue === "active"
+ ? fp.state === "FINALITY_PROVIDER_STATUS_ACTIVE"
+ : filterValue === "inactive"
+ ? fp.state !== "FINALITY_PROVIDER_STATUS_ACTIVE"
+ : true;
+
+ if (!statusMatch) return false;
+
+ if (searchValue) {
+ const searchLower = searchValue.toLowerCase();
+ return (
+ fp.description?.moniker?.toLowerCase().includes(searchLower) ||
+ fp.btcPk.toLowerCase().includes(searchLower)
+ );
+ }
+
+ return true;
+ });
+ }, [data?.finalityProviders, filterValue, searchValue]);
+
const getFinalityProviderMoniker = useCallback(
(btcPkHex: string) => {
- const moniker = data?.finalityProviders.find(
+ const moniker = filteredFinalityProviders.find(
(fp) => fp.btcPk === btcPkHex,
)?.description?.moniker;
@@ -55,17 +109,24 @@ export function useFinalityProviderService() {
}
return moniker;
},
- [data?.finalityProviders],
+ [filteredFinalityProviders],
);
return {
searchValue,
- finalityProviders: data?.finalityProviders,
+ filterValue,
+ finalityProviders: filteredFinalityProviders,
+ selectedFinalityProvider,
+ isFetching,
hasNextPage,
- isLoading: isFetchingNextPage,
- fetchNextPage,
+ hasError: isError,
handleSearch,
handleSort,
+ handleFilter,
+ handleRowSelect,
getFinalityProviderMoniker,
+ fetchNextPage,
};
}
+
+export { useFinalityProviderService } from "@/app/state/FinalityProviderServiceState";
diff --git a/src/app/state/FinalityProviderServiceState.tsx b/src/app/state/FinalityProviderServiceState.tsx
new file mode 100644
index 00000000..5868abd0
--- /dev/null
+++ b/src/app/state/FinalityProviderServiceState.tsx
@@ -0,0 +1,45 @@
+import { type PropsWithChildren } from "react";
+
+import { useFinalityProviderServiceState } from "@/app/hooks/services/useFinalityProviderService";
+import type { FinalityProvider } from "@/app/types/finalityProviders";
+import { createStateUtils } from "@/utils/createStateUtils";
+
+interface FinalityProviderServiceState {
+ searchValue: string;
+ filterValue: string | number;
+ finalityProviders: FinalityProvider[];
+ selectedFinalityProvider: FinalityProvider | null;
+ hasNextPage: boolean;
+ isFetching: boolean;
+ handleSearch: (searchTerm: string) => void;
+ handleSort: (sortField: string) => void;
+ handleFilter: (value: string | number) => void;
+ handleRowSelect: (row: FinalityProvider) => void;
+ getFinalityProviderMoniker: (btcPkHex: string) => string;
+ fetchNextPage: () => void;
+ hasError: boolean;
+}
+
+const { StateProvider, useState: useFinalityProviderService } =
+ createStateUtils({
+ searchValue: "",
+ filterValue: "active",
+ finalityProviders: [],
+ selectedFinalityProvider: null,
+ hasNextPage: false,
+ isFetching: false,
+ handleSearch: () => {},
+ handleSort: () => {},
+ handleFilter: () => {},
+ handleRowSelect: () => {},
+ getFinalityProviderMoniker: () => "-",
+ fetchNextPage: () => {},
+ hasError: false,
+ });
+
+export function FinalityProviderServiceState({ children }: PropsWithChildren) {
+ const state = useFinalityProviderServiceState();
+ return {children};
+}
+
+export { useFinalityProviderService };
diff --git a/src/app/state/index.tsx b/src/app/state/index.tsx
index 8bea3135..f4525ac0 100644
--- a/src/app/state/index.tsx
+++ b/src/app/state/index.tsx
@@ -15,8 +15,13 @@ import { NetworkInfo } from "../types/networkInfo";
import { DelegationState } from "./DelegationState";
import { DelegationV2State } from "./DelegationV2State";
+import { FinalityProviderServiceState } from "./FinalityProviderServiceState";
-const STATE_LIST = [DelegationState, DelegationV2State];
+const STATE_LIST = [
+ DelegationState,
+ DelegationV2State,
+ FinalityProviderServiceState,
+];
export interface AppState {
availableUTXOs?: UTXO[];
diff --git a/src/app/types/finalityProviders.ts b/src/app/types/finalityProviders.ts
index 6e53e52e..8c402574 100644
--- a/src/app/types/finalityProviders.ts
+++ b/src/app/types/finalityProviders.ts
@@ -17,8 +17,15 @@ export interface Description {
details: string;
}
+export enum FinalityProviderStatus {
+ FINALITY_PROVIDER_STATUS_ACTIVE = "Active",
+ FINALITY_PROVIDER_STATUS_INACTIVE = "Inactive",
+ FINALITY_PROVIDER_STATUS_JAILED = "Jailed",
+ FINALITY_PROVIDER_STATUS_SLASHED = "Slashed",
+}
+
export type FinalityProviderState =
- | "FINALITY_PROVIDER_STATUS_INACTIVE"
| "FINALITY_PROVIDER_STATUS_ACTIVE"
+ | "FINALITY_PROVIDER_STATUS_INACTIVE"
| "FINALITY_PROVIDER_STATUS_JAILED"
| "FINALITY_PROVIDER_STATUS_SLASHED";