Feat/project details page - #38
Conversation
- 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
📝 WalkthroughWalkthroughAdds 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
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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: 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
BountyListwill make a separate filtered request. For projects with many bounties, this duplicates data transfer.Options to consider:
- Add a dedicated endpoint to return available tags for a project.
- Pass the unfiltered data to
BountyListwhen no filters are active to avoid refetching.- 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.createdAtorproject.updatedAtcontain invalid date strings,new Date()will produce anInvalid Date, andformatDistanceToNowwill throw aRangeError. 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".
TrendingUptypically conveys growth or analytics. For a "Last Updated" timestamp,RefreshCw,Clock, orHistoryfrom 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.
ProjectMaintainersalready returnsnullwhenmaintainersis 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
Separatorto only appear when maintainers exist, consider moving the separator logic intoProjectMaintainersitself, or accept the current explicit check for clarity. The current approach is defensive and clear, so this is a minor style preference.
|
@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
552041e to
e2a963d
Compare
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
Bounties Section
Sidebar
Technical Implementation
Components Created
ProjectBounties: Client component with filtering systemProjectMaintainers: Displays maintainer informationProjectSidebar: Shows project metadata and linksAPI Enhancements
projectIdquery parameter for filtering bounties by projecttagsquery parameter for filtering bounties by tagsType Updates
Projecttype with optionalwebsiteUrlandmaintainersfieldsDesign
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.