Skip to content

Conversation

@Ekene001
Copy link
Contributor

@Ekene001 Ekene001 commented Jan 24, 2026

closes #14

  • Implement BountyCard as composition of shadcn Card component

  • Add grid and list layout variants with responsive design

  • Include bounty-specific data display: title, description, budget, status, category, claiming model, milestones

  • Add status indicators with semantic color coding (emerald for open, amber for claimed, blue for in-progress, red for disputed)

  • Display creator info with avatar, wallet, and timestamp

  • Show meta information: deadline countdown and applicant count

  • Implement smooth hover effects with shadow elevation and scale

  • Add text truncation for long titles and descriptions

  • Support click handlers for navigation

  • Ensure full mobile responsiveness:

    • Responsive padding and spacing (mobile-first)

    PROOF OF WORK:

image

Summary by CodeRabbit

  • New Features

    • Grid and list view variants for bounty cards.
    • GitHub-style bounty card showing issue metadata, tags, reward/difficulty, and external issue link.
    • Interactive, keyboard-accessible bounty cards with status, time, and click handling.
  • Refactor

    • Redesigned bounty card into a flexible, prop-driven component with centralized status presentation.
  • Style

    • Removed typography plugin and applied minor formatting/whitespace cleanups.

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

- Implement BountyCard as composition of shadcn Card component
- Add grid and list layout variants with responsive design
- Include bounty-specific data display: title, description, budget, status, category, claiming model, milestones
- Add status indicators with semantic color coding (emerald for open, amber for claimed, blue for in-progress, red for disputed)
- Display creator info with avatar, wallet, and timestamp
- Show meta information: deadline countdown and applicant count
- Implement smooth hover effects with shadow elevation and scale
- Add text truncation for long titles and descriptions
- Support click handlers for navigation
- Ensure full mobile responsiveness:
  - Responsive padding and spacing (mobile-first)
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

Adds a new BountyCard UI component built from shadcn/ui primitives, intended to support grid and list layouts with bounty-specific metadata and styling.

Changes:

  • Added components/bounty/bounty-card.tsx implementing BountyCard with status indicators, budget display, creator info, and responsive layout variants.
  • Minor formatting/whitespace changes in app/page.tsx.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
components/bounty/bounty-card.tsx Introduces the new BountyCard component with layout variants and bounty metadata rendering.
app/page.tsx Whitespace-only changes.

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

"overflow-hidden w-xs rounded-4xl cursor-pointer transition-all duration-300",
"hover:shadow-lg hover:border-primary/60 hover:scale-[1.02]",
"border border-slate-200 dark:border-slate-800",
variant === "list" && "flex flex-col md:flex-row",
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

In the list variant, md:flex-row is applied to the outer Card, which will place the footer beside the main content on medium screens (since CardFooter is a sibling). If the footer is intended to remain at the bottom, keep the Card as a column layout and only apply row layout to the internal content area.

Suggested change
variant === "list" && "flex flex-col md:flex-row",
variant === "list" && "flex flex-col",

Copilot uses AI. Check for mistakes.
"border border-slate-200 dark:border-slate-800",
variant === "list" && "flex flex-col md:flex-row",
)}
onClick={onClick}
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

Attaching onClick to a div-based Card makes it mouse-only by default. For accessibility, either render a semantic interactive element (e.g., <button>/<a> via asChild) or add role="button", tabIndex={0}, and keyboard handlers for Enter/Space (plus an accessible label if needed).

Copilot uses AI. Check for mistakes.
Comment on lines 4 to 13


export default async function Home() {
return (
<div className="flex justify-center items-center h-screen">
<SignIn />

</div>


Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

There are several extra blank lines added here that don’t change behavior but add noise to the diff. Please remove the redundant whitespace to keep the file clean.

Suggested change
export default async function Home() {
return (
<div className="flex justify-center items-center h-screen">
<SignIn />
</div>
export default async function Home() {
return (
<div className="flex justify-center items-center h-screen">
<SignIn />
</div>

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,232 @@
import * as React from "react";
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

This component attaches an onClick handler, which requires it to be a Client Component in the Next.js app router. Add a "use client"; directive at the top of the file (or remove the event handler and use a <Link>-based API instead).

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,232 @@
import * as React from "react";
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

import * as React from "react" is unused in this file and will fail linting with no-unused-vars. Remove it, or use React types (e.g., for keyboard events) if needed.

Suggested change
import * as React from "react";

Copilot uses AI. Check for mistakes.
import * as React from "react";
import {
Card,
CardContent,
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

CardContent is imported but never used, which will trigger lint/build failures. Remove the import or refactor the layout to use CardContent.

Suggested change
CardContent,

Copilot uses AI. Check for mistakes.
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Clock, Users, DollarSign } from "lucide-react";
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

DollarSign is imported from lucide-react but never used, which will trigger lint/build failures. Remove it or render it as part of the budget display.

Suggested change
import { Clock, Users, DollarSign } from "lucide-react";
import { Clock, Users } from "lucide-react";

Copilot uses AI. Check for mistakes.
Comment on lines 100 to 101
"overflow-hidden w-xs rounded-4xl cursor-pointer transition-all duration-300",
"hover:shadow-lg hover:border-primary/60 hover:scale-[1.02]",
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

The classes w-xs and rounded-4xl look like they may be no-ops with the current Tailwind theme setup (e.g., app/globals.css only defines radius tokens up to --radius-xl, and there’s no --radius-4xl). If these utilities aren’t defined, the card won’t size/round as intended. Prefer standard utilities (e.g., w-full/max-w-*, rounded-xl/rounded-3xl) or an explicit arbitrary value (e.g., rounded-[...]).

Copilot uses AI. Check for mistakes.
@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 two new BountyCard React components (generic and GitHub-specific) with grid/list variants, status handling, keyboard/click accessibility, metadata-driven rendering, removes a Tailwind typography plugin directive from app/globals.css, and applies whitespace-only formatting to app/page.tsx.

Changes

Cohort / File(s) Summary
Generic Bounty Card
components/bounty/bounty-card.tsx
New/exported BountyCard component and props; refactored to be prop-driven with variant (grid/list), onClick, statusConfig mapping, time-left computation, badges (category/model/milestones), avatar, reward/time/applicant meta, keyboard accessibility, and conditional layout.
GitHub Bounty Card
components/bounty/github-bounty-card.tsx
New client BountyCard component tailored for GitHub issue bounties: header (avatar/initials, repo/name, relative time), type/status badges, linked issue title (opens new tab), stripped body preview, tag overflow handling, reward/difficulty footer, and prevention of card navigation when interacting with inner interactive elements.
Styling / Globals
app/globals.css
Removed Tailwind Typography plugin directive (@plugin "@tailwindcss/typography";).
Formatting
app/page.tsx
Whitespace/blank-line adjustments only; no functional changes.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hop through props and stitch a card,
Badges gleam like carrots in the yard,
Grid or list I bound and play,
Deadlines tick and tags display,
A rabbit cheers — new UI, hooray!

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes an out-of-scope change: removing the Tailwind Typography plugin from app/globals.css, which is unrelated to building the BountyCard component. Revert the removal of the @tailwindcss/typography plugin directive from app/globals.css unless its removal is justified by a separate issue.
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 (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: building a BountyCard component using shadcn composition, which is the primary objective of the PR.
Linked Issues check ✅ Passed The PR addresses issue #14 by implementing a BountyCard component with shadcn composition, grid/list variants, status handling, and all required bounty data display features as specified.

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


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

@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #26

coderabbitai bot added a commit that referenced this pull request Jan 25, 2026
Docstrings generation was requested by @0xdevcollins.

* #17 (comment)

The following files were modified:

* `app/page.tsx`
* `components/bounty/bounty-card.tsx`
* `components/bounty/github-bounty-card.tsx`
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: 3

🤖 Fix all issues with AI agents
In `@components/bounty/bounty-card.tsx`:
- Around line 235-239: The applicant count is currently hidden for zero values;
update the render condition so it shows 0 (i.e., treat zero as valid). In the
BountyCard render where you check bounty.applicantCount != null &&
bounty.applicantCount > 0, change the predicate to allow zero (for example
bounty.applicantCount != null or typeof bounty.applicantCount === 'number' or
bounty.applicantCount >= 0) so the Users icon and
<span>{bounty.applicantCount}</span> are rendered even when the count is 0.
- Around line 180-186: The conditional rendering currently hides a "1 milestone"
label because it uses "bounty.milestoneCount && bounty.milestoneCount > 1";
change the condition to show the Badge when bounty.milestoneCount is defined and
>= 1 (e.g., bounty.milestoneCount && bounty.milestoneCount >= 1 or simply typeof
bounty.milestoneCount === 'number' && bounty.milestoneCount > 0) in the
component that renders the Badge, and update the Badge text to correctly
pluralize using bounty.milestoneCount === 1 ? '1 milestone' :
`${bounty.milestoneCount} milestones` so single-milestone bounties are
displayed.

In `@components/bounty/github-bounty-card.tsx`:
- Around line 1-8: This component is using an event handler
(handleInteractiveClick) and must be a Client Component, so add the directive
"use client"; as the very first line of the file (before any imports) in the
github-bounty-card component file; ensure the directive is the top-most token
and then keep the existing imports and the component (e.g., the Bounty/Github
link and handleInteractiveClick handler) unchanged so the onClick will run in
the client.
♻️ Duplicate comments (2)
app/page.tsx (1)

4-5: Remove redundant blank lines.
These whitespace-only changes add noise to the diff; consider reverting them.

Also applies to: 11-12

components/bounty/bounty-card.tsx (1)

99-103: Verify rounded-4xl/max-w-xs utilities exist.
If these tokens aren’t defined in Tailwind config, sizing/rounding won’t apply.

#!/bin/bash
# Check Tailwind config for custom tokens
rg -n "rounded-4xl|max-w-xs|borderRadius|maxWidth" -g 'tailwind.config.*'
🧹 Nitpick comments (1)
components/bounty/bounty-card.tsx (1)

98-113: Only add interactive semantics when onClick is provided.
Without a handler, the card still looks and behaves like a button to assistive tech.

Proposed refactor
 export function BountyCard({
   bounty,
   onClick,
   variant = "grid",
 }: BountyCardProps) {
   const status = statusConfig[bounty.status];
   const timeLeft = formatDistanceToNow(bounty.deadline, { addSuffix: true });
+  const isClickable = typeof onClick === "function";

   return (
     <Card
       className={cn(
-        "overflow-hidden w-full max-w-xs  rounded-4xl cursor-pointer transition-all duration-300",
+        "overflow-hidden w-full max-w-xs rounded-4xl transition-all duration-300",
         "hover:shadow-lg hover:border-primary/60 hover:scale-[1.02]",
         "border border-slate-200 dark:border-slate-800",
+        isClickable && "cursor-pointer",
         variant === "list" && "flex flex-col",
       )}
-      role="button"
-      tabIndex={0}
-      onClick={onClick}
-      onKeyDown={(e) => {
-        if (e.key === "Enter" || e.key === " ") {
-          e.preventDefault();
-          onClick?.();
-        }
-      }}
+      {...(isClickable && {
+        role: "button",
+        tabIndex: 0,
+        onClick,
+        onKeyDown: (e) => {
+          if (e.key === "Enter" || e.key === " ") {
+            e.preventDefault();
+            onClick?.();
+          }
+        },
+      })}
     >

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/bounty/bounty-card.tsx`:
- Around line 180-184: The milestone Badge is using malformed Tailwind arbitrary
values with backticks in the className (in the JSX that checks
bounty.milestoneCount and renders <Badge>), so Tailwind won't generate
bg-/border- styles; fix by removing the backticks and using proper arbitrary
syntax (e.g., bg-[`#f7fff0`] and border-[`#f2ffe5`]) while keeping the other classes
(text-xs px-3 py-1 dark:bg-slate-900 dark:border-slate-700) on the Badge
element.
♻️ Duplicate comments (1)
components/bounty/bounty-card.tsx (1)

99-103: Verify rounded-4xl exists in your Tailwind theme.

Line 100 uses rounded-4xl, which isn’t part of Tailwind’s default radius scale. If it’s not defined in your config, the radius will be a no‑op. Consider a defined token (e.g., rounded-3xl) or an arbitrary value (e.g., rounded-[2rem]) if you intended a custom size.

#!/bin/bash
# Locate Tailwind config(s) and inspect custom borderRadius tokens.
fd -t f -g 'tailwind.config.*'
rg -n 'borderRadius|radius' -g 'tailwind.config.*'

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/bounty/bounty-card.tsx`:
- Around line 99-104: The card always applies "max-w-xs" which prevents the list
variant from expanding; update the className construction in bounty-card.tsx
(the cn(...) call where variant === "list" is checked) to apply "max-w-xs" only
when variant !== "list" (or alternatively apply a full-width class like "w-full"
when variant === "list"), so the list variant can use the full available width
while preserving the compact cap for other variants.
- Around line 38-39: The local Bounty interface in BountyCard conflicts with the
canonical type and omits deadline, causing formatDistanceToNow(bounty.deadline)
to receive undefined; fix by removing the local interface and importing the
canonical Bounty from "@/types/bounty" (or alternatively add deadline: Date |
string to the canonical type in types/bounty.ts and ensure serialized dates are
handled), then update the BountyCard props and any uses of bounty.deadline
(e.g., in formatDistanceToNow) to accept Date | string and parse/convert strings
to Date before calling formatDistanceToNow.

Comment on lines 99 to 104
className={cn(
"overflow-hidden w-full max-w-xs rounded-4xl cursor-pointer transition-all duration-300",
"hover:shadow-lg hover:border-primary/60 hover:scale-[1.02]",
"border border-slate-200 dark:border-slate-800",
variant === "list" && "flex flex-col",
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

List variant is still capped to max-w-xs.
With max-w-xs applied unconditionally, the list layout won’t expand to full width, which undermines the list variant’s responsive intent.

💡 Proposed fix
-      className={cn(
-        "overflow-hidden w-full max-w-xs  rounded-4xl cursor-pointer transition-all duration-300",
+      className={cn(
+        "overflow-hidden w-full rounded-4xl cursor-pointer transition-all duration-300",
+        variant === "grid" && "max-w-xs",
+        variant === "list" && "max-w-none",
         "hover:shadow-lg hover:border-primary/60 hover:scale-[1.02]",
         "border border-slate-200 dark:border-slate-800",
         variant === "list" && "flex flex-col",
       )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className={cn(
"overflow-hidden w-full max-w-xs rounded-4xl cursor-pointer transition-all duration-300",
"hover:shadow-lg hover:border-primary/60 hover:scale-[1.02]",
"border border-slate-200 dark:border-slate-800",
variant === "list" && "flex flex-col",
)}
className={cn(
"overflow-hidden w-full rounded-4xl cursor-pointer transition-all duration-300",
variant === "grid" && "max-w-xs",
variant === "list" && "max-w-none",
"hover:shadow-lg hover:border-primary/60 hover:scale-[1.02]",
"border border-slate-200 dark:border-slate-800",
variant === "list" && "flex flex-col",
)}
🤖 Prompt for AI Agents
In `@components/bounty/bounty-card.tsx` around lines 99 - 104, The card always
applies "max-w-xs" which prevents the list variant from expanding; update the
className construction in bounty-card.tsx (the cn(...) call where variant ===
"list" is checked) to apply "max-w-xs" only when variant !== "list" (or
alternatively apply a full-width class like "w-full" when variant === "list"),
so the list variant can use the full available width while preserving the
compact cap for other variants.

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: 4

🤖 Fix all issues with AI agents
In `@components/bounty/bounty-card.tsx`:
- Around line 23-46: The statusConfig mapping and its use in BountyCard can
return undefined for unexpected bounty.status values; update statusConfig to
include all possible statuses (e.g., "in-progress", "disputed", etc.) and/or add
a safe fallback before rendering: compute a resolvedStatus =
statusConfig[bounty.status] ?? /* default mapping like closed/default */ and use
resolvedStatus in the component. Ensure the symbols referenced are statusConfig,
BountyCard and the variable status (or replace with resolvedStatus) so unknown
API statuses won't cause a render error.
- Around line 59-67: The card is being made focusable and keyboard-interactive
even when no onClick handler is provided; update the JSX in bounty-card.tsx to
conditionally add role="button", tabIndex={0}, and the onKeyDown keyboard
handler only when the onClick prop exists (i.e., when onClick is truthy). Locate
the element using the onClick prop and the onKeyDown inline handler, remove the
unconditional role/tabIndex, and wrap those attributes and the Enter/Space key
handling so they are only rendered/attached if onClick is defined.
- Around line 47-49: The timeLeft calculation uses bounty.updatedAt (showing
"last updated") instead of the bounty deadline/expiry; update the timeLeft
assignment to compute formatDistanceToNow(new Date(bounty.deadline ||
bounty.expiresAt), { addSuffix: true }) (or the exact deadline field used in the
bounty object) and fall back to "N/A" if that deadline/expiry field is missing,
keeping the variable name timeLeft and preserving usage of formatDistanceToNow.
- Around line 181-188: The footer currently only renders the Clock and timeLeft;
update the same metadata block in bounty-card.tsx to also render the applicant
count (showing 0 when missing) next to the time text. Add a span (matching the
existing text classes: "flex items-center gap-1 text-slate-600
dark:text-slate-400 whitespace-nowrap text-xs") after the timeLeft spans that
displays something like `{(applicantCount ?? 0)} applicant{(applicantCount ?? 0)
!== 1 ? "s" : ""}` (use the prop/state name used in this component for
applicants — e.g., applicantCount or applicants.length) and mirror the
responsive behavior (show full text on sm and truncated on mobile) so it appears
alongside timeLeft in the same div that contains Clock and timeLeft.
♻️ Duplicate comments (1)
components/bounty/bounty-card.tsx (1)

53-58: List variant is still constrained by max-w-xs.
This prevents the list layout from expanding to full width.

🧩 Proposed fix
-        "overflow-hidden w-full max-w-xs rounded-4xl cursor-pointer transition-all duration-300",
+        "overflow-hidden w-full rounded-4xl cursor-pointer transition-all duration-300",
+        variant === "grid" && "max-w-xs",
+        variant === "list" && "max-w-none",

Comment on lines +23 to +46
const statusConfig = {
open: {
variant: "default" as const,
label: "Open",
dotColor: "bg-emerald-500",
},
claimed: {
variant: "secondary" as const,
label: "Claimed",
dotColor: "bg-amber-500",
},
closed: {
variant: "outline" as const,
label: "Closed",
dotColor: "bg-slate-400",
},
};

const difficultyColors: Record<string, string> = {
beginner: "text-success-400",
intermediate: "text-warning-400",
advanced: "text-error-400",
}
export function BountyCard({
bounty,
onClick,
variant = "grid",
}: BountyCardProps) {
const status = statusConfig[bounty.status];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against unmapped bounty statuses.
statusConfig only covers open/claimed/closed. If the API sends another status (e.g., in‑progress/disputed per requirements), status becomes undefined and the render will throw. Add mappings for all statuses and/or a safe fallback.

🩹 Suggested hardening
-  const status = statusConfig[bounty.status];
+  const status =
+    statusConfig[bounty.status] ?? {
+      variant: "outline",
+      label: "Unknown",
+      dotColor: "bg-slate-400",
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const statusConfig = {
open: {
variant: "default" as const,
label: "Open",
dotColor: "bg-emerald-500",
},
claimed: {
variant: "secondary" as const,
label: "Claimed",
dotColor: "bg-amber-500",
},
closed: {
variant: "outline" as const,
label: "Closed",
dotColor: "bg-slate-400",
},
};
const difficultyColors: Record<string, string> = {
beginner: "text-success-400",
intermediate: "text-warning-400",
advanced: "text-error-400",
}
export function BountyCard({
bounty,
onClick,
variant = "grid",
}: BountyCardProps) {
const status = statusConfig[bounty.status];
const statusConfig = {
open: {
variant: "default" as const,
label: "Open",
dotColor: "bg-emerald-500",
},
claimed: {
variant: "secondary" as const,
label: "Claimed",
dotColor: "bg-amber-500",
},
closed: {
variant: "outline" as const,
label: "Closed",
dotColor: "bg-slate-400",
},
};
export function BountyCard({
bounty,
onClick,
variant = "grid",
}: BountyCardProps) {
const status =
statusConfig[bounty.status] ?? {
variant: "outline",
label: "Unknown",
dotColor: "bg-slate-400",
};
🤖 Prompt for AI Agents
In `@components/bounty/bounty-card.tsx` around lines 23 - 46, The statusConfig
mapping and its use in BountyCard can return undefined for unexpected
bounty.status values; update statusConfig to include all possible statuses
(e.g., "in-progress", "disputed", etc.) and/or add a safe fallback before
rendering: compute a resolvedStatus = statusConfig[bounty.status] ?? /* default
mapping like closed/default */ and use resolvedStatus in the component. Ensure
the symbols referenced are statusConfig, BountyCard and the variable status (or
replace with resolvedStatus) so unknown API statuses won't cause a render error.

Comment on lines +47 to +49
const timeLeft = bounty.updatedAt
? formatDistanceToNow(new Date(bounty.updatedAt), { addSuffix: true })
: "N/A";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clock should be based on the bounty deadline, not updatedAt.
This currently shows “last updated” rather than “time left,” which breaks the countdown requirement. Please use the deadline/expiry field instead.

🧭 Example update
-  const timeLeft = bounty.updatedAt
-    ? formatDistanceToNow(new Date(bounty.updatedAt), { addSuffix: true })
+  const timeLeft = bounty.deadline
+    ? formatDistanceToNow(new Date(bounty.deadline), { addSuffix: true })
     : "N/A";
🤖 Prompt for AI Agents
In `@components/bounty/bounty-card.tsx` around lines 47 - 49, The timeLeft
calculation uses bounty.updatedAt (showing "last updated") instead of the bounty
deadline/expiry; update the timeLeft assignment to compute
formatDistanceToNow(new Date(bounty.deadline || bounty.expiresAt), { addSuffix:
true }) (or the exact deadline field used in the bounty object) and fall back to
"N/A" if that deadline/expiry field is missing, keeping the variable name
timeLeft and preserving usage of formatDistanceToNow.

Comment on lines +59 to +67
role="button"
tabIndex={0}
onClick={onClick}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onClick?.();
}
}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid a focusable card when onClick is missing.
Right now the card is keyboard-focusable even if it does nothing. Gate role/tabIndex/keyboard handling on onClick.

♿ Suggested adjustment
-      role="button"
-      tabIndex={0}
+      role={onClick ? "button" : undefined}
+      tabIndex={onClick ? 0 : undefined}
       onClick={onClick}
       onKeyDown={(e) => {
-        if (e.key === "Enter" || e.key === " ") {
+        if (!onClick) return;
+        if (e.key === "Enter" || e.key === " ") {
           e.preventDefault();
-          onClick?.();
+          onClick();
         }
       }}
🤖 Prompt for AI Agents
In `@components/bounty/bounty-card.tsx` around lines 59 - 67, The card is being
made focusable and keyboard-interactive even when no onClick handler is
provided; update the JSX in bounty-card.tsx to conditionally add role="button",
tabIndex={0}, and the onKeyDown keyboard handler only when the onClick prop
exists (i.e., when onClick is truthy). Locate the element using the onClick prop
and the onKeyDown inline handler, remove the unconditional role/tabIndex, and
wrap those attributes and the Enter/Space key handling so they are only
rendered/attached if onClick is defined.

Comment on lines +181 to +188
<div className="flex items-center gap-2 sm:gap-3 flex-shrink-0 order-2 sm:order-none">
<div className="flex items-center gap-1 text-slate-600 dark:text-slate-400 whitespace-nowrap text-xs">
<Clock className="h-3.5 w-3.5 flex-shrink-0" />
<span className="hidden sm:inline">{timeLeft}</span>
<span className="sm:hidden">
{timeLeft.replace(" ago", "").replace(" from now", "")}
</span>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Applicant count is missing from footer meta.
Acceptance criteria call for displaying applicant count (including 0). The footer currently shows only the clock.

➕ Suggested addition
         <div className="flex items-center gap-2 sm:gap-3 flex-shrink-0 order-2 sm:order-none">
           <div className="flex items-center gap-1 text-slate-600 dark:text-slate-400 whitespace-nowrap text-xs">
             <Clock className="h-3.5 w-3.5 flex-shrink-0" />
             <span className="hidden sm:inline">{timeLeft}</span>
             <span className="sm:hidden">
               {timeLeft.replace(" ago", "").replace(" from now", "")}
             </span>
           </div>
+          {bounty.applicantCount != null && (
+            <div className="flex items-center gap-1 text-slate-600 dark:text-slate-400 whitespace-nowrap text-xs">
+              <Users className="h-3.5 w-3.5 flex-shrink-0" />
+              <span>{bounty.applicantCount}</span>
+            </div>
+          )}
         </div>
🤖 Prompt for AI Agents
In `@components/bounty/bounty-card.tsx` around lines 181 - 188, The footer
currently only renders the Clock and timeLeft; update the same metadata block in
bounty-card.tsx to also render the applicant count (showing 0 when missing) next
to the time text. Add a span (matching the existing text classes: "flex
items-center gap-1 text-slate-600 dark:text-slate-400 whitespace-nowrap
text-xs") after the timeLeft spans that displays something like
`{(applicantCount ?? 0)} applicant{(applicantCount ?? 0) !== 1 ? "s" : ""}` (use
the prop/state name used in this component for applicants — e.g., applicantCount
or applicants.length) and mirror the responsive behavior (show full text on sm
and truncated on mobile) so it appears alongside timeLeft in the same div that
contains Clock and timeLeft.

@0xdevcollins 0xdevcollins merged commit ee92a12 into boundlessfi:main Jan 25, 2026
2 checks passed
DanielEmmanuel1 pushed a commit to DanielEmmanuel1/bounties that referenced this pull request Jan 26, 2026
…onent-for-Bounty

feat: Built BountyCard component with shadcn composition
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.

Create card component for Bounty

2 participants