Skip to content

Conversation

@martinvibes
Copy link

@martinvibes martinvibes commented Jan 22, 2026

Description

feat: implement discover page with bounty and project cards, filtering, and sorting capabilities.

closes #3

Screen.Recording.2026-01-22.at.1.mp4

Summary by CodeRabbit

  • New Features
    • Added a Discover page with Projects and Bounties tabs.
    • Search, tag filters, and sorting (newest, recently updated, highest reward) with URL-synced state and debounced updates.
    • Responsive filter panel for desktop and mobile with clear filters control and active-count badge.
    • New Project and Bounty cards with status, tags, reward/progress, metadata, loading skeletons, and empty-state UIs.
    • Included sample data for development/testing.

✏️ Tip: You can customize this high-level summary in your review settings.

@martinvibes
Copy link
Author

@0xdevcollins pls kindly review and merge

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements a new /discover page that lists mock Projects and Bounties with card UIs and client-side filtering/sorting, with filter state reflected in the URL.

Changes:

  • Added shared types/config (tabs, sort options, available tags) and mock datasets for projects/bounties.
  • Introduced filter UI components (search, tags, sort) and card components for projects/bounties.
  • Created the Discover page with tabs, skeleton loading state, filtering/sorting logic, and empty states.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
lib/types.ts Adds shared type definitions and UI config for tags/tabs/sort options.
lib/mock-data.ts Provides mock project and bounty datasets for the discover experience.
components/filters/tags-filter.tsx Adds a tag-selection popover filter UI.
components/filters/sort-dropdown.tsx Adds sort selection dropdown with optional hidden options.
components/filters/search-filter.tsx Adds debounced search input for filtering.
components/filters/filter-panel.tsx Composes search/tags/sort controls for desktop + mobile sheet.
components/cards/project-card.tsx Adds project card presentation and navigation link.
components/cards/bounty-card.tsx Adds bounty card presentation and navigation link.
app/discover/page.tsx Implements the discover page (tabs, filtering/sorting, URL sync, empty/loading states).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@0xdevcollins
Copy link
Contributor

0xdevcollins commented Jan 24, 2026

@martinvibes implement the copilot corrections

@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a client-side Discover page with Projects/Bounties tabs, shared filter components, URL-synced debounced state, mock data, and new Project/Bounty card UIs plus supporting types and mock data.

Changes

Cohort / File(s) Summary
Discover Page
app/discover/page.tsx
New client-side page implementing tabbed Projects/Bounties view, filter integration, debounced URL query sync (300ms), client-side filtering/sorting, loading skeletons, and empty-state UIs.
Cards
components/cards/project-card.tsx, components/cards/bounty-card.tsx
New exported ProjectCard and BountyCard components rendering item metadata, tags, status/difficulty badges, relative timestamps, and optional progress/deadline UI.
Filters
components/filters/filter-panel.tsx, components/filters/search-filter.tsx, components/filters/sort-dropdown.tsx, components/filters/tags-filter.tsx
New FilterPanel (desktop/mobile), debounced SearchFilter (300ms), SortDropdown (hides options per tab), and TagsFilter (popover with checkboxes and clear).
Types & Mock Data
lib/types.ts, lib/mock-data.ts
Adds domain types (TabType, SortOption, FilterState, Project, Bounty, statuses), constants (AVAILABLE_TAGS, SORT_OPTIONS, TABS), and sample mockProjects/mockBounties.
IDE config
.vscode/settings.json
Added empty VSCode settings JSON file.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Discover as DiscoverPage
    participant Filter as FilterPanel
    participant Router as Router/URL
    participant Data as Client Filtering/MockData
    participant Cards as Card Components

    User->>Discover: Open page / click tab
    Discover->>Router: Read URL params (tab, search, tags, sort)
    Discover->>Discover: Initialize state from params
    Discover->>Filter: Render filters with state

    User->>Filter: Update search/tags/sort
    Filter->>Discover: onFiltersChange(newFilters)
    Discover->>Router: Debounced replace URL (300ms)
    Discover->>Data: Apply filters & sort to mock data
    Data->>Cards: Provide filtered/sorted items
    Cards->>User: Render ProjectCard / BountyCard list

    alt No results
        Cards->>User: Show EmptyState
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰
Tabs click, filters hop in line,
Projects, bounties — tidy and fine.
Mock data dances through every test,
Debounced URLs keep states dressed.
A little rabbit applauds the new design.

🚥 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 accurately describes the main changes: implementing a discover page with bounty and project cards, filtering, and sorting capabilities.
Linked Issues check ✅ Passed The PR implements all key requirements from issue #3: tab-based discovery page [#3], shared modular filters (Search, Tags, Sort) [#3], URL state synchronization [#3], and responsive UI with loading/empty states [#3].
Out of Scope Changes check ✅ Passed All changes directly support the discover page implementation. The .vscode/settings.json file is a minimal configuration addition not directly related to PR objectives but is a common development convenience tool.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@martinvibes
Copy link
Author

@0xdevcollins i have fixed it ser
pls be kind to give me some compliment points 🤲

Copy link

@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: 2

🤖 Fix all issues with AI agents
In `@components/cards/project-card.tsx`:
- Around line 49-51: The ProjectCard component is styled as interactive (uses
className "block group" plus "cursor-pointer" and hover classes on the Card) but
has no navigation; either wrap the component in a Next.js Link to
`/projects/${project.id}` (import Link from "next/link" and replace the outer
div with a Link or wrap the Card so clicking navigates to
`/projects/${project.id}` while preserving the existing className and Card
props), or remove the interactive classes ("cursor-pointer" and the hover:...
classes on the Card) so the UI no longer implies clickability; locate this
change in components/cards/project-card.tsx and update the outer container or
Card accordingly.

In `@components/filters/sort-dropdown.tsx`:
- Around line 24-30: The Sort dropdown can end up with a hidden current value
(e.g., "highestReward") when tabs change; update SortDropdown (look for
visibleOptions, SORT_OPTIONS, hiddenOptions, value and onChange usage) to handle
this by adding fallback logic: on mount and whenever value or hiddenOptions
change, if value is not in visibleOptions call onChange with the first
visibleOptions[0] (or default "newest") so the UI always shows a valid option;
alternatively implement a useEffect in the parent that watches activeTab and
resets filters.sort to "newest" when the tab changes (update wherever
filters.sort is set).
🧹 Nitpick comments (5)
components/cards/bounty-card.tsx (1)

63-65: Guard against unexpected status/difficulty values.
If runtime data ever includes an unsupported status or difficulty, statusConfig[...] becomes undefined and status.icon will throw. Consider a safe fallback or an explicit “Unknown” entry.

♻️ Suggested fallback (example)
-  const status = statusConfig[bounty.status];
-  const difficulty = difficultyConfig[bounty.difficulty];
+  const status = statusConfig[bounty.status] ?? statusConfig.open;
+  const difficulty =
+    difficultyConfig[bounty.difficulty] ?? difficultyConfig.beginner;
app/discover/page.tsx (3)

144-145: Use useMemo instead of calling filter functions on every render.

filteredProjects() and filteredBounties() are invoked on every render, defeating the purpose of useCallback. The callbacks themselves are memoized, but their results are not.

♻️ Suggested refactor using useMemo
-  const filteredProjects = useCallback(() => {
+  const filteredProjects = useMemo(() => {
     let result = [...mockProjects];
     // ... filtering logic ...
     return result;
-  }, [filters]);
+  }, [filters]);

-  const filteredBounties = useCallback(() => {
+  const filteredBounties = useMemo(() => {
     let result = [...mockBounties];
     // ... filtering logic ...
     return result;
-  }, [filters]);
+  }, [filters]);

-  const projects = filteredProjects();
-  const bounties = filteredBounties();
+  const projects = filteredProjects;
+  const bounties = filteredBounties;

147-162: Move EmptyState outside the component to avoid recreation on each render.

Defining components inside the render function creates a new component identity on every render, which can cause unnecessary unmount/remount cycles and break React's reconciliation.

♻️ Suggested refactor

Move EmptyState above DiscoverPage:

const EmptyState = ({ type }: { type: "projects" | "bounties" }) => (
  <div className="flex flex-col items-center justify-center py-16 px-4 text-center">
    <div className="mb-4 p-6 bg-primary/5 rounded-full">
      {type === "projects" ? (
        <PackageOpen className="h-16 w-16 text-primary/50" />
      ) : (
        <Coins className="h-16 w-16 text-primary/50" />
      )}
    </div>
    <h3 className="text-xl font-semibold mb-2">No {type} found</h3>
    <p className="text-muted-foreground max-w-md">
      Try adjusting your filters or search terms to find what you&#39;re
      looking for.
    </p>
  </div>
);

export default function DiscoverPage() {
  // ...
}

This also eliminates the need for the eslint-disable comment on line 239.


183-186: Validate tab value in onValueChange for consistency.

The initial tab parameter is validated using isValidTab, but the onValueChange handler casts the value directly without validation. While the Tabs component should only emit valid values, this creates an inconsistency.

♻️ Suggested fix
         <Tabs
           value={activeTab}
-          onValueChange={(val) => setActiveTab(val as TabType)}
+          onValueChange={(val) => {
+            if (isValidTab(val)) setActiveTab(val);
+          }}
         >
components/filters/filter-panel.tsx (1)

103-106: Mobile badge count doesn't reflect sort filter changes.

hasActiveFilters (line 46-47) considers sort !== "newest" as an active filter, but the mobile badge count only sums search and tags. If a user changes only the sort option, the badge shows "0" while the "Clear filters" button appears.

♻️ Suggested fix for consistency
               {hasActiveFilters && (
                 <span className="ml-2 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
-                  {(filters.search ? 1 : 0) + filters.tags.length}
+                  {(filters.search ? 1 : 0) + filters.tags.length + (filters.sort !== "newest" ? 1 : 0)}
                 </span>
               )}

Copy link

@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

🤖 Fix all issues with AI agents
In `@components/cards/project-card.tsx`:
- Around line 43-103: Normalize project.completedMilestones into a local
variable (e.g., completedOrZero) and use it both for the progress calculation
(instead of repeating project.completedMilestones) and for the label that
currently renders "{project.completedMilestones} of {project.milestones}
milestones", so the UI never shows "undefined"; update the existing progress
constant (progress) to use this normalized value and replace the label's
project.completedMilestones reference with the same variable.
♻️ Duplicate comments (1)
components/cards/project-card.tsx (1)

49-51: Card looks interactive but has no navigation.

The hover styling + group wrapper imply clickability, but there’s no Link or click handler. Either add navigation or remove the interactive styling to avoid a misleading UX.

@martinvibes
Copy link
Author

@0xdevcollins i have fixed all of it
pls be kind to give me some compliment points 🤲

@0xdevcollins 0xdevcollins merged commit baa39f4 into boundlessfi:main Jan 28, 2026
2 checks passed
@martinvibes
Copy link
Author

martinvibes commented Jan 28, 2026

@0xdevcollins pls be kind to give me some compliment points 🤲

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.

Bounties & Projects Listing Page (Tabs + Shared Filters)

2 participants