feat: Implement API-driven bounty fetching and display with dedicated loading, empty, and error UI components. - #34
Conversation
… loading, empty, and error UI components.
📝 WalkthroughWalkthroughAdds a GET API route for fetching and filtering bounties and refactors the bounty UI to use a Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant BountyList as BountyList (component)
participant Hook as useBounties (query hook)
participant API as /api/bounties (Next API)
participant Data as MockDataSource
Browser->>BountyList: mount / user interaction
BountyList->>Hook: call useBounties(params)
Hook->>API: GET /api/bounties?status=...&type=...&difficulty=...&search=...
API->>Data: fetch all bounties
Data-->>API: returns list
API->>API: apply filters (status, type, difficulty, search)
API-->>Hook: 200 JSON { data, pagination } (after ~500ms)
Hook-->>BountyList: resolves data / isLoading false / isError false
BountyList-->>Browser: render grid of BountyCard (or skeleton / error / empty)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/bounty/page.tsx`:
- Around line 30-31: The temporary array reference for allBounties (from
useBounties()) causes dependent useMemo hooks to re-run; replace the simple
assignment with a stable memoized value by computing allBounties via useMemo and
depending on the fetched payload (e.g., data?.data) so the reference only
changes when the actual data changes—update the declaration of allBounties to
use useMemo(() => data?.data ?? [], [data?.data]) so the downstream useMemo
hooks that reference allBounties are stable.
🧹 Nitpick comments (3)
app/api/bounties/route.ts (2)
7-8: Remove or conditionally apply the artificial delay for production.The 500ms delay is useful for development to test loading states, but it will degrade user experience in production. Consider making it conditional based on environment.
♻️ Suggested fix
- // Simulate network delay - await new Promise(resolve => setTimeout(resolve, 500)); + // Simulate network delay in development only + if (process.env.NODE_ENV === 'development') { + await new Promise(resolve => setTimeout(resolve, 500)); + }
15-22: Consider validating filter parameter values.The filter parameters (
status,type,difficulty) accept any string value without validation. While the current implementation will simply return no matches for invalid values, explicit validation could provide better error feedback and prevent unexpected behavior.♻️ Example validation approach
const VALID_STATUSES = ['open', 'claimed', 'closed']; const VALID_TYPES = ['feature', 'bug', 'documentation', 'refactor', 'other']; const VALID_DIFFICULTIES = ['beginner', 'intermediate', 'advanced']; const status = searchParams.get('status'); if (status && !VALID_STATUSES.includes(status)) { return NextResponse.json({ error: 'Invalid status parameter' }, { status: 400 }); }app/bounty/page.tsx (1)
374-388: Consider reusing theBountyEmptycomponent for consistency.The empty state UI here duplicates the
BountyEmptycomponent that was introduced in this PR. Using the shared component would ensure visual consistency and reduce duplication.♻️ Suggested refactor
First, add the import at the top of the file:
import { BountyEmpty } from "@/components/bounty/bounty-empty"Then replace the inline empty state:
) : ( - <div className="flex flex-col items-center justify-center py-24 text-center border border-dashed border-gray-800 rounded-2xl bg-background-card/30"> - <div className="size-16 rounded-full bg-gray-800/50 flex items-center justify-center mb-4"> - <Search className="size-8 text-gray-600" /> - </div> - <h3 className="text-xl font-bold mb-2 text-gray-200">No bounties found</h3> - <p className="text-gray-400 max-w-md mx-auto mb-6"> - We couldn't find any bounties matching your current filters. - Try adjusting your search terms or filters. - </p> - <Button onClick={clearFilters} variant="outline" className="border-gray-700 hover:bg-gray-800"> - Clear all filters - </Button> - </div> + <BountyEmpty hasFilters={true} onClearFilters={clearFilters} /> )}
…etch-bounties feat: Implement API-driven bounty fetching and display with dedicated loading, empty, and error UI components.
This pull request introduces a new API route for fetching bounties with filter support and refactors the bounty listing UI to use a new data-fetching hook and reusable UI states. It adds skeleton loading, error, and empty state components for a better user experience and centralizes bounty list rendering into a new
BountyListcomponent.API and Data Fetching Improvements:
app/api/bounties/route.tsthat returns a filtered list of bounties based on query parameters such as status, type, difficulty, and search terms.app/bounty/page.tsxto use the newuseBountieshook for data fetching instead of importing mock data directly. [1] [2]UI/UX Enhancements:
BountyCardSkeleton,BountyListSkeleton), error (BountyError), and empty (BountyEmpty) state components for the bounty list, improving feedback during loading, error, or empty results. [1] [2] [3]Component Abstraction:
BountyListcomponent that encapsulates data fetching, filtering, and all UI states, allowing for easier reuse and cleaner page code.SCREEN RECORDING
Screen.Recording.2026-01-25.at.15.24.02.mov
closes #33
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.