-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Built BountyCard component with shadcn composition #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Built BountyCard component with shadcn composition #17
Conversation
- 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)
There was a problem hiding this 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.tsximplementingBountyCardwith 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.
components/bounty/bounty-card.tsx
Outdated
| "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", |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
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.
| variant === "list" && "flex flex-col md:flex-row", | |
| variant === "list" && "flex flex-col", |
| "border border-slate-200 dark:border-slate-800", | ||
| variant === "list" && "flex flex-col md:flex-row", | ||
| )} | ||
| onClick={onClick} |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
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).
|
|
||
|
|
||
| export default async function Home() { | ||
| return ( | ||
| <div className="flex justify-center items-center h-screen"> | ||
| <SignIn /> | ||
|
|
||
| </div> | ||
|
|
||
|
|
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
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.
| 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> |
components/bounty/bounty-card.tsx
Outdated
| @@ -0,0 +1,232 @@ | |||
| import * as React from "react"; | |||
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
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).
components/bounty/bounty-card.tsx
Outdated
| @@ -0,0 +1,232 @@ | |||
| import * as React from "react"; | |||
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
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.
| import * as React from "react"; |
components/bounty/bounty-card.tsx
Outdated
| import * as React from "react"; | ||
| import { | ||
| Card, | ||
| CardContent, |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
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.
| CardContent, |
components/bounty/bounty-card.tsx
Outdated
| } 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"; |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
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.
| import { Clock, Users, DollarSign } from "lucide-react"; | |
| import { Clock, Users } from "lucide-react"; |
components/bounty/bounty-card.tsx
Outdated
| "overflow-hidden w-xs rounded-4xl cursor-pointer transition-all duration-300", | ||
| "hover:shadow-lg hover:border-primary/60 hover:scale-[1.02]", |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
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-[...]).
|
Note Other AI code review bot(s) detectedCodeRabbit 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. 📝 WalkthroughWalkthroughAdds 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 Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 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. Comment |
|
Note Docstrings generation - SUCCESS |
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`
There was a problem hiding this 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: Verifyrounded-4xl/max-w-xsutilities 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 whenonClickis 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?.(); + } + }, + })} >
There was a problem hiding this 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: Verifyrounded-4xlexists 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.*'
There was a problem hiding this 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.
| 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", | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this 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 bymax-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",
| 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]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| const timeLeft = bounty.updatedAt | ||
| ? formatDistanceToNow(new Date(bounty.updatedAt), { addSuffix: true }) | ||
| : "N/A"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| role="button" | ||
| tabIndex={0} | ||
| onClick={onClick} | ||
| onKeyDown={(e) => { | ||
| if (e.key === "Enter" || e.key === " ") { | ||
| e.preventDefault(); | ||
| onClick?.(); | ||
| } | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
…card-component-for-Bounty
…onent-for-Bounty feat: Built BountyCard component with shadcn composition
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:
PROOF OF WORK:
Summary by CodeRabbit
New Features
Refactor
Style
✏️ Tip: You can customize this high-level summary in your review settings.