Skip to content

refactor: modularize BountiesPage and fix status filter bug (#127)#165

Open
KaruG1999 wants to merge 1 commit intoboundlessfi:mainfrom
KaruG1999:refactor/issue-127-modularize-bounty-page
Open

refactor: modularize BountiesPage and fix status filter bug (#127)#165
KaruG1999 wants to merge 1 commit intoboundlessfi:mainfrom
KaruG1999:refactor/issue-127-modularize-bounty-page

Conversation

@KaruG1999
Copy link
Copy Markdown

@KaruG1999 KaruG1999 commented Mar 27, 2026

Summary: Transformed the monolithic BountiesPage into a clean, orchestrated architecture using the Atomic Design pattern.

Key Changes:

  • Custom Hook: Extracted state management and filtering logic into useBountyFilters.
  • Atomic Components: Created FiltersSidebar, BountyToolbar, and BountyGrid to separate concerns.
  • Bug Fix: Fixed a logic error where the 'Reset' button wouldn't appear due to a case-sensitivity mismatch (OPEN vs open).
  • Testing: Added 21 unit tests covering all filter, search, and sorting edge cases.

Result: page.tsx reduced from 411 to 72 lines. All UI behavior remains identical but with improved maintainability.

bountyUI1 bountiesTest

closes #127

Summary by CodeRabbit

  • New Features

    • Dedicated filter sidebar: keyword search, bounty type, organization, reward range, and status controls with a visible Reset button.
    • Sort toolbar: shows total results and a “Sort by” selector (newest, highest reward, recently updated).
    • Results grid: improved loading skeleton, clearer error message with retry, and improved empty-state panel with a clear-filters action.
  • Improvements

    • Faster, consistent filtering, sorting, and responsive layout for bounty listings.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 27, 2026

@KaruG1999 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave
Copy link
Copy Markdown

drips-wave bot commented Mar 27, 2026

@KaruG1999 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

Moved bounty filtering, sorting, and UI responsibilities out of the monolithic page into a new hook and reusable components: useBountyFilters, FiltersSidebar, BountyToolbar, and BountyGrid; added tests for the hook and simplified app/bounty/page.tsx to compose those pieces.

Changes

Cohort / File(s) Summary
Page Refactor
app/bounty/page.tsx
Replaced local filter state and handlers with useBountyFilters(allBounties) and now composes FiltersSidebar, BountyToolbar, and BountyGrid; page reduced to coordinator logic.
Filtering Hook
hooks/use-bounty-filters.ts
New hook exporting BOUNTY_TYPES, STATUSES, and useBountyFilters(allBounties); manages search, selected types/orgs, reward range, status, sort, exposes toggles/clear and derives filteredBounties + organizations.
Filters UI
components/bounty/filters-sidebar.tsx
New controlled sidebar component rendering search input, status select, type/org checklists, reward slider, conditional Reset button, and MiniLeaderboard; receives full filter state and callbacks.
Toolbar
components/bounty/bounty-toolbar.tsx
New toolbar component showing result count and "Sort by" select with newest, highest_reward, recently_updated; controlled via props.
Grid / Results
components/bounty/bounty-grid.tsx
New grid component handling loading, error, empty-state, and rendering bounty cards linked to /bounty/{id}; accepts data + status callbacks.
Tests
hooks/__tests__/use-bounty-filters.test.ts
New comprehensive Jest tests for useBountyFilters: defaults, search/type/org/reward/status filtering, sorting behaviors, toggles, and clearFilters reset behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Page as app/bounty/page.tsx
  participant Hook as useBountyFilters
  participant Sidebar as FiltersSidebar
  participant Toolbar as BountyToolbar
  participant Grid as BountyGrid

  Page->>Hook: call useBountyFilters(allBounties)
  Hook-->>Page: return { filteredBounties, state, setters, hasActiveFilters }
  Page->>Sidebar: render with state + setters (search/toggle/org/reward/status/clear)
  Page->>Toolbar: render with totalCount + sort state + onSortChange
  Page->>Grid: render with filteredBounties, isLoading, isError, onRetry, onClearFilters
  Sidebar->>Hook: (via props) invoke onSearch/onToggle/onReward/onClear
  Toolbar->>Hook: (via props) invoke onSortChange
  Grid->>Page: onClearFilters / onRetry callbacks propagate back to Hook/Page
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Benjtalkshow
  • 0xdevcollins

Poem

🐇
I hopped through lines of tangled state,
Pulled threads apart to make things straight.
Hooks and panels, tidy and bright —
Filters found and sorted right.
A carrot-coded springtime sight! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the two main changes: the refactoring/modularization of BountiesPage and the status filter bug fix (#127), matching the PR's primary objectives.
Linked Issues check ✅ Passed All requirements from issue #127 are met: page reduced from 411 to 72 lines, FiltersSidebar/BountyGrid/BountyToolbar created with clear types, single responsibilities assigned, status filter bug fixed, and functionality preserved with 21 passing tests.
Out of Scope Changes check ✅ Passed All changes directly support issue #127 requirements: modularizing BountiesPage, extracting filter/toolbar/grid components, fixing the status filter bug, and adding comprehensive test coverage for the hook.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
components/bounty/bounty-toolbar.tsx (1)

11-15: Consider using a union type for sortOption to improve type safety.

Using a string union type instead of string would catch invalid sort values at compile time and provide better IDE autocompletion.

♻️ Proposed type improvement
+type SortOption = "newest" | "highest_reward" | "recently_updated";
+
 interface BountyToolbarProps {
   totalCount: number;
-  sortOption: string;
-  onSortChange: (value: string) => void;
+  sortOption: SortOption;
+  onSortChange: (value: SortOption) => void;
 }

This type could be exported from use-bounty-filters.ts and reused across components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty/bounty-toolbar.tsx` around lines 11 - 15, Replace the loose
string type for sortOption in BountyToolbarProps with a string union of the
allowed sort keys (e.g., 'newest' | 'oldest' | 'mostPopular' etc.), export that
union type from use-bounty-filters.ts (e.g., BountySortOption) and import it
into components/bounty/bounty-toolbar.tsx; update the prop signature
onSortChange to accept the union type (onSortChange: (value: BountySortOption)
=> void) so both the prop and any callers are type-safe and get IDE autocomplete
for valid sort values.
components/bounty/filters-sidebar.tsx (1)

190-199: Remove redundant defaultValue on controlled Slider.

The Slider has both defaultValue and value props. Since value is provided, this is a controlled component and defaultValue is ignored.

♻️ Remove redundant prop
                     <Slider
-                      defaultValue={[0, 5000]}
                       max={5000}
                       step={100}
                       value={[rewardRange[0], rewardRange[1]]}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty/filters-sidebar.tsx` around lines 190 - 199, The Slider is
currently controlled (it uses the value prop), so remove the redundant
defaultValue prop; locate the Slider component in filters-sidebar.tsx (the JSX
using Slider with value={[rewardRange[0], rewardRange[1]]} and onValueChange
calling onRewardRangeChange) and delete the defaultValue={[0, 5000]} attribute,
leaving the controlled value and onValueChange logic (keep the existing fallback
val[1] ?? 5000 if desired).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/use-bounty-filters.ts`:
- Around line 59-60: The status comparison in matchesStatus fails due to case
mismatch between statusFilter (lowercase) and bounty.status (uppercase); update
the comparison so both sides use the same case—either normalize statusFilter to
uppercase when storing it via setStatusFilter or compare bounty.status to an
uppercased statusFilter in the matchesStatus expression (i.e., use statusFilter
transformed to uppercase), referencing the statusFilter variable, the
bounty.status property, the matchesStatus expression, and the setStatusFilter
setter to locate the code.

---

Nitpick comments:
In `@components/bounty/bounty-toolbar.tsx`:
- Around line 11-15: Replace the loose string type for sortOption in
BountyToolbarProps with a string union of the allowed sort keys (e.g., 'newest'
| 'oldest' | 'mostPopular' etc.), export that union type from
use-bounty-filters.ts (e.g., BountySortOption) and import it into
components/bounty/bounty-toolbar.tsx; update the prop signature onSortChange to
accept the union type (onSortChange: (value: BountySortOption) => void) so both
the prop and any callers are type-safe and get IDE autocomplete for valid sort
values.

In `@components/bounty/filters-sidebar.tsx`:
- Around line 190-199: The Slider is currently controlled (it uses the value
prop), so remove the redundant defaultValue prop; locate the Slider component in
filters-sidebar.tsx (the JSX using Slider with value={[rewardRange[0],
rewardRange[1]]} and onValueChange calling onRewardRangeChange) and delete the
defaultValue={[0, 5000]} attribute, leaving the controlled value and
onValueChange logic (keep the existing fallback val[1] ?? 5000 if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d1e6113d-f470-4ccd-b447-37cbda7df524

📥 Commits

Reviewing files that changed from the base of the PR and between 3aad250 and 8da94ab.

📒 Files selected for processing (6)
  • app/bounty/page.tsx
  • components/bounty/bounty-grid.tsx
  • components/bounty/bounty-toolbar.tsx
  • components/bounty/filters-sidebar.tsx
  • hooks/__tests__/use-bounty-filters.test.ts
  • hooks/use-bounty-filters.ts

@KaruG1999 KaruG1999 force-pushed the refactor/issue-127-modularize-bounty-page branch from 8da94ab to a895f0b Compare March 28, 2026 00:04
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/bounty/page.tsx (1)

37-53: Narrow the FiltersSidebar prop surface.

This keeps page.tsx smaller, but the sidebar still needs a large number of tightly related props. Every new filter now expands both sides of the boundary. Consider grouping these into typed values, options, and actions objects so the composition layer stays stable as filters grow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/bounty/page.tsx` around lines 37 - 53, The FiltersSidebar prop list is
too wide; refactor page.tsx and FiltersSidebar to accept three grouped props
(e.g., values, options, actions) instead of many individual props: move current
state read-only items (filters.searchQuery, filters.selectedTypes,
filters.selectedOrgs, filters.rewardRange, filters.statusFilter,
filters.organizations) into a single values object, move static choices
(BOUNTY_TYPES, STATUSES) into an options object, and move callbacks
(filters.setSearchQuery, filters.toggleType, filters.toggleOrg,
filters.setRewardRange, filters.setStatusFilter, filters.clearFilters,
filters.hasActiveFilters) into an actions object; update the FiltersSidebar
component signature and its prop types to accept these grouped objects and
update its internal references to use values.*, options.*, and actions.* so the
page composition stays stable as filters grow, while keeping existing symbol
names (FiltersSidebar, filters, BOUNTY_TYPES, STATUSES, setSearchQuery,
toggleType, toggleOrg, setRewardRange, setStatusFilter, clearFilters) for easy
location.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/bounty/page.tsx`:
- Around line 56-60: The toolbar is receiving a misleading totalCount from
filters.filteredBounties during load/error; change the prop so BountyToolbar
only gets a numeric total when the query is successful and the count is
known—i.e., compute totalCount as filters.filteredBounties.length only when
filters indicates success (e.g., !filters.isLoading && !filters.isError /
filters.status === 'success'), otherwise pass undefined/null (or omit the prop)
so BountyToolbar can render an indeterminate/loading state; update the call site
where BountyToolbar is rendered (the BountyToolbar totalCount prop) and ensure
you reference filters.filteredBounties, filters.isLoading/filters.isError (or
filters.status) and BountyToolbar to locate the change.

---

Nitpick comments:
In `@app/bounty/page.tsx`:
- Around line 37-53: The FiltersSidebar prop list is too wide; refactor page.tsx
and FiltersSidebar to accept three grouped props (e.g., values, options,
actions) instead of many individual props: move current state read-only items
(filters.searchQuery, filters.selectedTypes, filters.selectedOrgs,
filters.rewardRange, filters.statusFilter, filters.organizations) into a single
values object, move static choices (BOUNTY_TYPES, STATUSES) into an options
object, and move callbacks (filters.setSearchQuery, filters.toggleType,
filters.toggleOrg, filters.setRewardRange, filters.setStatusFilter,
filters.clearFilters, filters.hasActiveFilters) into an actions object; update
the FiltersSidebar component signature and its prop types to accept these
grouped objects and update its internal references to use values.*, options.*,
and actions.* so the page composition stays stable as filters grow, while
keeping existing symbol names (FiltersSidebar, filters, BOUNTY_TYPES, STATUSES,
setSearchQuery, toggleType, toggleOrg, setRewardRange, setStatusFilter,
clearFilters) for easy location.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6a5e396-2e35-4905-9dee-f39a2281b0de

📥 Commits

Reviewing files that changed from the base of the PR and between 8da94ab and a895f0b.

📒 Files selected for processing (6)
  • app/bounty/page.tsx
  • components/bounty/bounty-grid.tsx
  • components/bounty/bounty-toolbar.tsx
  • components/bounty/filters-sidebar.tsx
  • hooks/__tests__/use-bounty-filters.test.ts
  • hooks/use-bounty-filters.ts
✅ Files skipped from review due to trivial changes (2)
  • components/bounty/bounty-toolbar.tsx
  • hooks/tests/use-bounty-filters.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • components/bounty/bounty-grid.tsx
  • hooks/use-bounty-filters.ts
  • components/bounty/filters-sidebar.tsx

Comment on lines +56 to +60
<BountyToolbar
totalCount={filters.filteredBounties.length}
sortOption={filters.sortOption}
onSortChange={filters.setSortOption}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don't render a fake 0 results state while the query is loading or failed.

BountyToolbar only knows about totalCount, so during the initial fetch and on errors it can only render the empty-array fallback from filters.filteredBounties. That makes the page look like a real empty result set when the data is actually unknown.

Suggested fix
           <main className="flex-1 min-w-0">
-            <BountyToolbar
-              totalCount={filters.filteredBounties.length}
-              sortOption={filters.sortOption}
-              onSortChange={filters.setSortOption}
-            />
+            {!isLoading && !isError && (
+              <BountyToolbar
+                totalCount={filters.filteredBounties.length}
+                sortOption={filters.sortOption}
+                onSortChange={filters.setSortOption}
+              />
+            )}
             <BountyGrid
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/bounty/page.tsx` around lines 56 - 60, The toolbar is receiving a
misleading totalCount from filters.filteredBounties during load/error; change
the prop so BountyToolbar only gets a numeric total when the query is successful
and the count is known—i.e., compute totalCount as
filters.filteredBounties.length only when filters indicates success (e.g.,
!filters.isLoading && !filters.isError / filters.status === 'success'),
otherwise pass undefined/null (or omit the prop) so BountyToolbar can render an
indeterminate/loading state; update the call site where BountyToolbar is
rendered (the BountyToolbar totalCount prop) and ensure you reference
filters.filteredBounties, filters.isLoading/filters.isError (or filters.status)
and BountyToolbar to locate the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modularize BountiesPage Component

1 participant