Skip to content

Feat/project details page - #38

Merged
0xdevcollins merged 8 commits into
boundlessfi:mainfrom
respp:feat/project-details-page
Jan 28, 2026
Merged

Feat/project details page#38
0xdevcollins merged 8 commits into
boundlessfi:mainfrom
respp:feat/project-details-page

Conversation

@respp

@respp respp commented Jan 25, 2026

Copy link
Copy Markdown
Contributor

Project Details Page Implementation Summary

Overview

Implemented a comprehensive Project Details page that displays full project information and lists all relevant bounties for that project, providing contributors with context and a clear path to contribute.

Video

Link: https://www.loom.com/share/90ee51772fb04cd5ace7f9fd2b317c57

Key Features

Project Information

  • Header Section: Project logo, name, website link, tags, and maintainers
  • Stats Cards: Display open bounties, total bounties, and prize pool
  • Full Description: Markdown-supported project description with proper styling
  • Maintainers: Optional section showing project maintainers with avatars and profile links

Bounties Section

  • Filtered Bounties: Display bounties filtered by project ID
  • Advanced Filters: Filter by type, difficulty, status, and tags
  • Dynamic Tags: Automatically extract and display available tags from project bounties
  • Responsive Grid: Bounty cards displayed in a responsive grid layout

Sidebar

  • Project Stats: Total/open bounties and prize pool
  • Links: Website link when available
  • Timeline: Creation and last update dates with relative time

Technical Implementation

Components Created

  • ProjectBounties: Client component with filtering system
  • ProjectMaintainers: Displays maintainer information
  • ProjectSidebar: Shows project metadata and links

API Enhancements

  • Added projectId query parameter for filtering bounties by project
  • Added tags query parameter for filtering bounties by tags
  • Updated API types to support new filtering options

Type Updates

  • Extended Project type with optional websiteUrl and maintainers fields
  • Maintains backward compatibility with existing code

Design

  • Responsive two-column layout (main content + sidebar)
  • Dark theme with green accent colors
  • Consistent with existing UI structure
  • Loading, error, and empty states handled gracefully

Summary by CodeRabbit

  • New Features

    • Redesigned project page with a two-column layout and persistent sidebar (stats, links, timeline)
    • Project sidebar, maintainers section, and "About This Project" rich-text area
    • ProjectBounties UI with multi-criteria filtering (type, difficulty, status, tags) and project-scoped results
    • Project website links shown with external affordance
  • Chores

    • Extended mock data and project types to include website URLs and maintainers for richer display

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

respp added 7 commits January 25, 2026 13:31
- Add optional websiteUrl field to Project type
- Add optional maintainers array with user info (userId, username, avatarUrl, profileUrl)
- Matches expected data types from issue requirements
- Add websiteUrl to active projects (boundless, soroban-kit, stellar-privacy-lab)
- Add sample maintainers data with avatars and profile links
- Maintains backward compatibility with existing project structure
- Add 8 new bounties distributed across different projects
- Include bounties for boundless, soroban-kit, and stellar-privacy-lab
- Cover various types (feature, bug, documentation, refactor)
- Include different difficulty levels and statuses for testing
- Add projectId query parameter to filter bounties by project
- Add tags query parameter to filter bounties by tags (comma-separated)
- Update BountyListParams type to include tags field
- Convert tags array to comma-separated string in API client
- Display maintainer avatars and usernames
- Link to GitHub profiles when profileUrl is available
- Only renders when maintainers array exists and has items
- Follows existing UI design patterns
- Display project stats (total/open bounties, prize pool)
- Show website link if available
- Display timeline (created/updated dates with relative time)
- Sticky positioning on desktop for better UX
- Uses Card components for consistent styling
- Display bounties filtered by projectId
- Add filters for type, difficulty, status, and tags
- Dynamically extract available tags from project bounties
- Integrate with BountyList component for consistent display
- Include clear filters functionality
- Handle loading, error, and empty states
@coderabbitai

coderabbitai Bot commented Jan 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a responsive Project Details page with a sidebar and new components for bounties, maintainers, and project stats; extends the bounties API and client param handling to support projectId and multi-tag filtering; and augments mock data and types with websiteUrl and maintainers.

Changes

Cohort / File(s) Summary
API & Types
app/api/bounties/route.ts, lib/api/bounties.ts, types/project.ts
Added projectId and multi-tag (tags) filtering to the bounties API handler; extended BountyListParams to accept tags; added websiteUrl and maintainers to Project type.
Project Page & UI Components
app/projects/[id]/page.tsx, components/projects/project-bounties.tsx, components/projects/project-maintainers.tsx, components/projects/project-sidebar.tsx
Reworked project detail page to a responsive two-column layout with a 320px sidebar. Added ProjectBounties (client-side multi-criteria filters: type, difficulty, status, tags), ProjectMaintainers (avatar/listing with optional links), and ProjectSidebar (stats, links, timeline).
Mock Data
lib/mock-bounty.ts, lib/mock-project.ts
Expanded mock bounties (many new entries) and augmented mock projects with websiteUrl and maintainers data.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Browser as Browser (Project Page)
participant UI as ProjectBounties Component
participant API as Bounties API (app/api/bounties/route.ts)
participant Data as Mock/Data Layer
Browser->>UI: Load project page (projectId)
UI->>API: GET /api/bounties?projectId=...&tags=...&status=...&type=...&difficulty=...
API->>Data: Query/filter by projectId, tags, status, type, difficulty, search
Data-->>API: Return filtered bounty list
API-->>UI: JSON bounties
UI-->>Browser: Render filters + filtered BountyList

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through code with eager paws,

New tags and sidebars fixed the flaws,
Bounties filtered, maintainers shown,
A shinier project page has grown,
Cheers from a rabbit—hop, rejoice! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/project details page' is directly related to the main objective of the PR, which implements a comprehensive Project Details page with project information, bounties filtering, maintainers, and sidebar.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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/projects/project-bounties.tsx`:
- Around line 100-113: The Badge elements rendered inside the bountyTypes.map
are currently non-focusable spans with only onClick, so keyboard users cannot
select filters; update the rendering of Badge (used with bountyTypes,
selectedType, setSelectedType and cn) to be keyboard-accessible by either
wrapping the Badge in a semantic <button>, or using the Badge's asChild prop to
render a button element, or adding tabIndex={0} plus an onKeyDown handler that
triggers setSelectedType on Enter/Space; ensure focus/active visual styles
remain and the key handler mirrors the onClick behavior for the same
selectedType value.

In `@lib/mock-bounty.ts`:
- Around line 101-132: Update the projectId on the existing bounty objects with
id "1", "2", and "3" in lib/mock-bounty.ts from "boundless-finance" to
"boundless" so they match the Boundless project defined in lib/mock-project.ts
and align with the new bounties (ids "4"–"11"); locate the bounty entries by
their id fields and change only the projectId value to "boundless".
🧹 Nitpick comments (6)
app/api/bounties/route.ts (1)

27-35: Consider case-insensitive tag matching.

The current implementation uses exact string matching for tags, which is case-sensitive. If tag casing varies between the UI and data (e.g., "Bug" vs "bug"), matches will fail silently.

♻️ Optional: case-insensitive tag matching
     const tags = searchParams.get('tags');
     if (tags) {
-        const tagArray = tags.split(',').filter(Boolean);
+        const tagArray = tags.split(',').filter(Boolean).map(t => t.toLowerCase());
         if (tagArray.length > 0) {
             filtered = filtered.filter(b => 
-                tagArray.some(tag => b.tags.includes(tag))
+                tagArray.some(tag => b.tags.some(t => t.toLowerCase() === tag))
             );
         }
     }
components/projects/project-bounties.tsx (1)

43-52: Consider caching or backend support for available tags.

This fetches all project bounties just to extract unique tags, while BountyList will make a separate filtered request. For projects with many bounties, this duplicates data transfer.

Options to consider:

  1. Add a dedicated endpoint to return available tags for a project.
  2. Pass the unfiltered data to BountyList when no filters are active to avoid refetching.
  3. Accept the trade-off for simplicity if bounty counts remain low.
types/project.ts (1)

17-22: Consider extracting the Maintainer type for reusability.

The inline maintainer object type is clear, but extracting it could improve reusability if maintainer data is used elsewhere (e.g., in API responses, other components).

♻️ Optional: Extract Maintainer type
+export type Maintainer = {
+  userId: string;
+  username: string;
+  avatarUrl?: string;
+  profileUrl?: string;
+};
+
 export type Project = {
   id: string;
   name: string;
   logoUrl: string | null;
   websiteUrl?: string;
   description: string;
   tags: string[];
   bountyCount: number;
   openBountyCount: number;
   creatorName: string;
   creatorAvatarUrl: string | null;
   prizeAmount: string;
   status: "Active" | "Ended" | "Draft";
   bannerUrl: string | null;
   createdAt: string;
   updatedAt: string;
-  maintainers?: Array<{
-    userId: string;
-    username: string;
-    avatarUrl?: string;
-    profileUrl?: string;
-  }>;
+  maintainers?: Maintainer[];
 };
components/projects/project-sidebar.tsx (2)

12-14: Consider handling invalid date strings defensively.

If project.createdAt or project.updatedAt contain invalid date strings, new Date() will produce an Invalid Date, and formatDistanceToNow will throw a RangeError. While mock data is currently valid, real API data might not be.

🛡️ Optional defensive handling
 export function ProjectSidebar({ project }: ProjectSidebarProps) {
-  const createdTimeAgo = formatDistanceToNow(new Date(project.createdAt), { addSuffix: true });
-  const updatedTimeAgo = formatDistanceToNow(new Date(project.updatedAt), { addSuffix: true });
+  const formatTimeAgo = (dateStr: string): string => {
+    try {
+      const date = new Date(dateStr);
+      if (isNaN(date.getTime())) return "Unknown";
+      return formatDistanceToNow(date, { addSuffix: true });
+    } catch {
+      return "Unknown";
+    }
+  };
+
+  const createdTimeAgo = formatTimeAgo(project.createdAt);
+  const updatedTimeAgo = formatTimeAgo(project.updatedAt);

75-81: Consider using a more semantically appropriate icon for "Last Updated".

TrendingUp typically conveys growth or analytics. For a "Last Updated" timestamp, RefreshCw, Clock, or History from lucide-react would better convey the concept of recent activity or modification time.

app/projects/[id]/page.tsx (1)

87-92: Redundant maintainers check - the component already handles this.

ProjectMaintainers already returns null when maintainers is empty or undefined (lines 10-12 in project-maintainers.tsx). The outer conditional and Separator could be simplified.

♻️ Simplified approach (optional)

If you want the Separator to only appear when maintainers exist, consider moving the separator logic into ProjectMaintainers itself, or accept the current explicit check for clarity. The current approach is defensive and clear, so this is a minor style preference.

Comment thread components/projects/project-bounties.tsx
Comment thread lib/mock-bounty.ts
@0xdevcollins

Copy link
Copy Markdown
Contributor

@respp resolve thre coderabbit review

@respp

respp commented Jan 27, 2026

Copy link
Copy Markdown
Contributor Author

@respp resolve thre coderabbit review

Suggestions applied! Please review

- Add website link in header with icon and hover effects
- Render project description with Markdown support using react-markdown
- Display project header with logo, name, tags, and maintainers
- Show stats cards (open/total bounties, prize pool)
- Add full description section with CTA to bounties
- Integrate ProjectBounties component with filters
- Add ProjectSidebar with metadata and links
- Implement responsive two-column layout (main content + sidebar)
- Follow existing UI structure and design patterns
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.

Build Project Details Page (Project Info + Available Bounties)

2 participants