diff --git a/hooks/use-bounties.ts b/hooks/use-bounties.ts
index f515fa5..b7355f9 100644
--- a/hooks/use-bounties.ts
+++ b/hooks/use-bounties.ts
@@ -4,27 +4,51 @@ import {
type BountyFieldsFragment,
} from "@/lib/graphql/generated";
import { bountyKeys } from "@/lib/query/query-keys";
+import { formatPaginatedBounties } from "@/lib/utils/pagination";
export { bountyKeys };
+/**
+ * Hook for fetching a paginated list of bounties from GraphQL API
+ *
+ * This hook wraps the generated `useBountiesQuery` and transforms the GraphQL
+ * PaginatedBounties response to the internal PaginatedResponse format expected by UI components.
+ *
+ * @param params - Query parameters including page, limit, filters, and search
+ * @returns Object containing bounties data, pagination info, and query status (loading, error, etc.)
+ *
+ * @example
+ * const { data, isLoading, error } = useBounties({
+ * page: 1,
+ * limit: 20,
+ * search: "security"
+ * });
+ *
+ * if (isLoading) return ;
+ * if (error) return ;
+ *
+ * return (
+ *
+ * {data?.data.map(bounty =>
)}
+ *
+ *
+ * );
+ */
export function useBounties(params?: BountyQueryInput) {
const { data, ...rest } = useBountiesQuery({ query: params });
return {
...rest,
data: data
- ? {
- data: data.bounties.bounties as BountyFieldsFragment[],
- pagination: {
- page: params?.page ?? 1,
- limit: data.bounties.limit,
- total: data.bounties.total,
- totalPages:
- data.bounties.limit > 0
- ? Math.ceil(data.bounties.total / data.bounties.limit)
- : 0,
- },
- }
+ ? formatPaginatedBounties(
+ data.bounties.bounties as BountyFieldsFragment[],
+ data.bounties.total,
+ data.bounties.limit,
+ params?.page ?? 1,
+ )
: undefined,
};
}
diff --git a/hooks/use-bounty-search.ts b/hooks/use-bounty-search.ts
index 0eb8d09..1a35b51 100644
--- a/hooks/use-bounty-search.ts
+++ b/hooks/use-bounty-search.ts
@@ -10,9 +10,60 @@ import {
} from "@/lib/graphql/generated";
import { bountyKeys } from "@/lib/query/query-keys";
+/** LocalStorage key for persisting recent searches */
const RECENT_SEARCHES_KEY = "bounties-recent-searches";
+/** Maximum number of recent searches to persist */
const MAX_RECENT_SEARCHES = 5;
+/**
+ * Hook for searching bounties with debounced GraphQL queries
+ *
+ * Provides a complete search experience with:
+ * - Debounced search queries (300ms delay)
+ * - Recent searches persistence using localStorage
+ * - Loading and fetching state management
+ * - Keyboard-friendly open/close toggle
+ *
+ * The search is disabled until the user opens the search dialog and enters text,
+ * preventing unnecessary API calls.
+ *
+ * @returns Object containing search state, results, and management functions:
+ * - searchTerm: Current search input text
+ * - setSearchTerm: Update search text
+ * - debouncedSearch: Debounced search term sent to API
+ * - isOpen: Whether search dialog is open
+ * - setIsOpen: Set search dialog visibility
+ * - toggleOpen: Toggle search dialog open/closed
+ * - results: Array of bounty results from GraphQL API
+ * - isLoading: Whether initial query is loading
+ * - recentSearches: Array of previously searched terms
+ * - addRecentSearch: Save a term to recent searches
+ * - removeRecentSearch: Remove a term from recent searches
+ * - clearRecentSearches: Clear all recent searches
+ *
+ * @example
+ * const {
+ * searchTerm,
+ * setSearchTerm,
+ * results,
+ * isLoading,
+ * recentSearches
+ * } = useBountySearch();
+ *
+ * return (
+ *
+ *
+ * {isLoading ? (
+ *
+ * ) : (
+ * <>
+ * {recentSearches.length > 0 && }
+ * {results.length > 0 && }
+ * >
+ * )}
+ *
+ * );
+ */
export function useBountySearch() {
const [searchTerm, setSearchTerm] = useState("");
const [isOpen, setIsOpen] = useState(false);
diff --git a/hooks/use-infinite-bounties.ts b/hooks/use-infinite-bounties.ts
index 72e2d62..3d96cfd 100644
--- a/hooks/use-infinite-bounties.ts
+++ b/hooks/use-infinite-bounties.ts
@@ -8,9 +8,48 @@ import {
} from "@/lib/graphql/generated";
import { type PaginatedResponse } from "@/lib/api/types";
import { bountyKeys } from "@/lib/query/query-keys";
+import { formatPaginatedBounties } from "@/lib/utils/pagination";
+/** Default number of bounties to fetch per page */
const DEFAULT_LIMIT = 20;
+/**
+ * Hook for fetching bounties with infinite scroll pagination from GraphQL API
+ *
+ * This hook uses React Query's `useInfiniteQuery` to manage paginated data fetching
+ * with automatic pagination handling. It fetches the next page of results as needed
+ * and maintains accumulated data across pages.
+ *
+ * The hook automatically manages:
+ * - Pagination state (current page, items per page)
+ * - Loading and error states
+ * - Next/previous page determination
+ * - Result accumulation and flattening
+ *
+ * @param params - Query parameters (page is omitted, determined by pagination)
+ * @returns Infinite query result with pages array and pagination functions
+ *
+ * @example
+ * const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteBounties({
+ * limit: 20,
+ * search: "security"
+ * });
+ *
+ * const allBounties = data?.pages.flatMap(page => page.data) ?? [];
+ *
+ * return (
+ * }
+ * >
+ * {allBounties.map(bounty => (
+ *
+ * ))}
+ *
+ * );
+ */
export function useInfiniteBounties(params?: Omit) {
return useInfiniteQuery>({
queryKey: bountyKeys.infinite(params),
@@ -25,16 +64,13 @@ export function useInfiniteBounties(params?: Omit) {
limit: params?.limit ?? DEFAULT_LIMIT,
},
})();
- const data = response.bounties;
- return {
- data: data.bounties as BountyFieldsFragment[],
- pagination: {
- page: pageParam as number,
- limit: data.limit,
- total: data.total,
- totalPages: data.limit > 0 ? Math.ceil(data.total / data.limit) : 0,
- },
- };
+ const paginatedData = response.bounties;
+ return formatPaginatedBounties(
+ paginatedData.bounties as BountyFieldsFragment[],
+ paginatedData.total,
+ paginatedData.limit,
+ pageParam as number,
+ );
},
initialPageParam: 1,
getNextPageParam: (lastPage) => {
diff --git a/lib/utils/pagination.ts b/lib/utils/pagination.ts
new file mode 100644
index 0000000..4ead3f5
--- /dev/null
+++ b/lib/utils/pagination.ts
@@ -0,0 +1,53 @@
+/**
+ * Pagination utility functions for GraphQL responses
+ * Converts GraphQL PaginatedBounties response to the internal PaginatedResponse format
+ */
+
+import type { PaginatedResponse } from "@/lib/api/types";
+import type { BountyFieldsFragment } from "@/lib/graphql/generated";
+
+/**
+ * Calculates the total number of pages from total items and limit
+ * @param total - Total number of items
+ * @param limit - Items per page
+ * @returns Total number of pages, or 0 if limit is 0 or invalid
+ */
+export function calculateTotalPages(total: number, limit: number): number {
+ return limit > 0 ? Math.ceil(total / limit) : 0;
+}
+
+/**
+ * Transforms a GraphQL PaginatedBounties response to the internal PaginatedResponse format
+ * Handles the mapping of GraphQL response structure to UI expectations
+ *
+ * @param bounties - Array of bounty items from GraphQL response
+ * @param total - Total count of bounties
+ * @param limit - Items per page
+ * @param page - Current page number
+ * @returns PaginatedResponse with bounties array and pagination metadata
+ *
+ * @example
+ * const response = await bounties query...
+ * const formatted = formatPaginatedBounties(
+ * response.bounties.bounties,
+ * response.bounties.total,
+ * response.bounties.limit,
+ * 1
+ * );
+ */
+export function formatPaginatedBounties(
+ bounties: BountyFieldsFragment[],
+ total: number,
+ limit: number,
+ page: number,
+): PaginatedResponse {
+ return {
+ data: bounties,
+ pagination: {
+ page,
+ limit,
+ total,
+ totalPages: calculateTotalPages(total, limit),
+ },
+ };
+}