Conversation
|
@Belzabeem is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a dispute workflow: UI to raise disputes from bounty CTAs, a dispute review page for authorized reviewers, React Query hooks for raise/resolve mutations, status typing for disputed states, and UI/status styling updates for disputed submissions. Changes
Sequence DiagramsequenceDiagram
participant Contributor
participant ClientUI
participant Server
participant Database
rect rgba(100, 150, 200, 0.5)
Note over Contributor,ClientUI: Dispute Raise Flow
Contributor->>ClientUI: Click "Raise Dispute"
ClientUI->>ClientUI: Open dialog, collect reason
Contributor->>ClientUI: Submit reason
ClientUI->>Server: RaiseDispute mutation
Server->>Database: Create dispute record
Database-->>Server: Return dispute
Server-->>ClientUI: Mutation success
ClientUI->>ClientUI: Close dialog, show toast, invalidate caches
end
rect rgba(150, 100, 200, 0.5)
Note over Reviewer,ClientUI: Dispute Resolution Flow
Reviewer->>ClientUI: Open DisputeReviewPage (disputeId)
ClientUI->>Server: Query dispute details
Server->>Database: Fetch dispute, bounty, submissions
Database-->>Server: Return data
Server-->>ClientUI: Respond with dispute context
Reviewer->>ClientUI: Click resolve action / request info
ClientUI->>Server: ResolveDispute mutation (or create request)
Server->>Database: Update dispute + related entities
Database-->>Server: Update complete
Server-->>ClientUI: Mutation success
ClientUI->>Reviewer: Show toast, invalidate caches
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@Belzabeem Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Benjtalkshow
left a comment
There was a problem hiding this comment.
Hey @Belzabeem, a few things to fix before this can merge.
The raise-dispute flow won't actually work. raiseDispute isn't in the GraphQL schema; the backend only exposes admin-side operations (assignDispute, escalateDispute, resolveDispute). MutationRaiseDisputeArgs and CreateDisputeInput also don't exist in lib/graphql/generated.ts, which is part of why CI/CD fails with lint errors. Please confirm with the team if the backend mutation is in progress, otherwise this can't ship.
The dispute review page is in the wrong place. You put it at components/bounty-detail/page.tsx, which isn't a route — issue #134 asked for app/dispute/[disputeId]/page.tsx. Right now nobody can navigate to the page you built.
There's also a regression in MobileCTA: the isFcfs ? <FcfsClaimButton /> branch from #161 was lost in the merge, so FCFS bounties on mobile no longer show the Claim button. Around lines 449–475 the JSX looks malformed (probably a bad conflict resolution), and that's the main reason the lint step blows up.
On the data model side, reason in CreateDisputeInput is a DisputeReasonEnum (DEADLINE_MISSED, POOR_QUALITY_WORK, etc.), not free text. It should be a <Select>, and the textarea value belongs in description. On resolve, "approve contributor" maps to DISMISSED and "approve sponsor" to FULL_REFUND — DISMISSED doesn't mean the contributor won, please double-check DisputeResolutionEnum. The "Request Info" button is currently a no-op (only fires a success toast), and pnpm-lock.yaml is out of sync with package.json, so run pnpm install and commit.
Once these are in, happy to take another look. Thanks!
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/bounty-detail/bounty-detail-sidebar-cta.tsx (1)
425-476:⚠️ Potential issue | 🔴 Critical
MobileCTAreturn is structurally broken — this file will not compile.Walking the JSX:
- Line 449 opens
{canCancel && (with a single JSX child expected.- Lines 450–464 render a duplicate "label"
<Button>(identical in behavior to the button at lines 428–438 — a copy/paste remnant).- Line 465 opens a nested
{canCancel && (…)}as a sibling of the Button inside the already-opencanCancelblock — two sibling expressions inside a conditional without a fragment is a syntax error.- Line 475
</div>closes the outer<div className="flex gap-2">from inside thecanCancelblock, so whencanCancel === falsethe wrapper div is never closed.- Paren balance also breaks: the outer
canCancelopens on 449 and closes on 476, but the intervening structure doesn't make sense.This entire block needs to be rewritten. Intended layout is probably: one row with [primary CTA, optional dispute button, optional cancel button].
🐛 Proposed rewrite
- <div className="flex gap-2"> - <Button - className="flex-1 h-11 font-bold tracking-wide" - disabled={!canAct} - size="lg" - onClick={() => - canAct && - window.open(bounty.githubIssueUrl, "_blank", "noopener,noreferrer") - } - > - {label()} - </Button> - {canDispute && ( - <Button - variant="outline" - size="lg" - className="h-11 border-yellow-500/30 text-yellow-500 hover:bg-yellow-500/10 shrink-0" - onClick={() => setDisputeDialogOpen(true)} - > - <AlertTriangle className="size-4" /> - </Button> - )} - {canCancel && ( - <Button - className="flex-1 h-11 font-bold tracking-wide" - disabled={!canAct} - size="lg" - onClick={() => - canAct && - window.open( - bounty.githubIssueUrl, - "_blank", - "noopener,noreferrer", - ) - } - > - {label()} - </Button> - {canCancel && ( - <Button - variant="outline" - size="lg" - className="h-11 border-red-500/30 text-red-400 hover:bg-red-500/10 shrink-0" - onClick={() => setCancelDialogOpen(true)} - > - <XCircle className="size-4" /> - </Button> - )} - </div> - )} + <div className="flex gap-2"> + <Button + className="flex-1 h-11 font-bold tracking-wide" + disabled={!canAct} + size="lg" + onClick={() => + canAct && + window.open(bounty.githubIssueUrl, "_blank", "noopener,noreferrer") + } + > + {label()} + </Button> + {canDispute && bounty.status !== "DISPUTED" && ( + <Button + variant="outline" + size="lg" + className="h-11 border-yellow-500/30 text-yellow-500 hover:bg-yellow-500/10 shrink-0" + onClick={() => setDisputeDialogOpen(true)} + > + <AlertTriangle className="size-4" /> + </Button> + )} + {canCancel && ( + <Button + variant="outline" + size="lg" + className="h-11 border-red-500/30 text-red-400 hover:bg-red-500/10 shrink-0" + onClick={() => setCancelDialogOpen(true)} + > + <XCircle className="size-4" /> + </Button> + )} + </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 425 - 476, The JSX return for the mobile CTA is malformed: remove the duplicated primary Button and rewrite the conditional rendering so the outer wrapper <div className="flex gap-2"> is always closed and each optional control is a sibling; specifically, in the MobileCTA return (the block using canAct, canDispute, canCancel, label, bounty.githubIssueUrl) keep one primary Button that opens bounty.githubIssueUrl, then render the dispute Button when canDispute (onClick -> setDisputeDialogOpen(true)), and render the cancel outline Button when canCancel (onClick -> setCancelDialogOpen(true)), ensuring each conditional returns a single element (no nested same-condition blocks) and all JSX tags are properly balanced and closed.
🧹 Nitpick comments (5)
components/bounty-detail/page.tsx (1)
112-123: Variable shadowing:errorinsidehandleResolveshadows the outererrorfromuseQuery.Line 91's
catch (error)shadows theerrordestructured on line 47. Not a bug, but renaming (e.g.catch (err)) avoids confusion and matches the pattern used elsewhere in this PR (e.g. sidebar CTA).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/page.tsx` around lines 112 - 123, In handleResolve, the catch block currently uses "catch (error)" which shadows the outer "error" from the useQuery destructuring; rename the catch parameter (e.g., to "err" or "resolveErr") and update any references inside the catch so they use the new name, ensuring consistency with other handlers (like the sidebar CTA) and avoiding confusion with the outer "error" variable.components/bounty-detail/bounty-detail-submissions-card.tsx (1)
150-162: Minor style inconsistency for the new badge.
DISPUTEDadds aborder border-yellow-200whileAPPROVED/REJECTED/PENDINGrely on background-only. If that border was intentional to distinguish the state, ignore; otherwise drop it for visual parity with the other badges.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-submissions-card.tsx` around lines 150 - 162, The DISPUTED case in getStatusBadgeColor currently returns "bg-yellow-100 text-yellow-900 border border-yellow-200" which adds an extra border unlike the other statuses; update the DISPUTED branch in the getStatusBadgeColor function to return only the background and text classes (e.g., "bg-yellow-100 text-yellow-900") so it matches the visual style of APPROVED/REJECTED/PENDING.components/bounty-detail/bounty-detail-sidebar-cta.tsx (2)
97-102:?? falseat the end of a&&chain is redundant.
isContributorisboolean | undefined(from optional-chained.some(...)), butA && BwhereAis a strictbooleanalready coerces the result — and the final type isboolean | undefined. The explicit?? falseonly covers the undefined path; you can get the same behavior more cheaply withBoolean(...)or by defaultingisContributor:- const isContributor = bounty.submissions?.some( - (s) => s.submittedBy === session?.user?.id - ); + const isContributor = + bounty.submissions?.some((s) => s.submittedBy === session?.user?.id) ?? false; @@ - (isCreator || isContributor)) ?? false; + (isCreator || isContributor);Also aligns the desktop computation with the mobile one (which lacks the
?? false).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 97 - 102, The expression computing canDispute uses a redundant "?? false" after an && chain; replace that with a boolean coercion to match the mobile logic and avoid unnecessary nullish fallback. Locate the canDispute assignment (references: canDispute, bounty.status, isCreator, isContributor) and change it to return a strict boolean by wrapping the whole logical expression with Boolean(...) (or prefixing with !!), or alternatively default isContributor when it's produced (e.g., ensure isContributor is boolean by using ?? false where it's defined); this yields the same behavior without the trailing "?? false".
55-55: RedundantisDisputingstate — prefermutation.isPending.
useMutationalready tracks pending state onraiseDisputeMutation.isPending(v5 API), and the dialog’sdisabledattribute at line 342 / 550 already includes it. The extraisDisputinguseState mirrors the same signal and adds two renders per submit. Dropping it simplifies the handler and removes a source of divergence between the two pieces of state.♻️ Proposed refactor (apply in both CTAs)
- const [isDisputing, setIsDisputing] = useState(false); - const raiseDisputeMutation = useRaiseDisputeMutation(); + const raiseDisputeMutation = useRaiseDisputeMutation(); + const isDisputing = raiseDisputeMutation.isPending; @@ const handleRaiseDispute = async () => { - setIsDisputing(true); try { await raiseDisputeMutation.mutateAsync({ /* ... */ }); toast.success("Dispute raised successfully..."); setDisputeDialogOpen(false); setDisputeReason(""); } catch (error) { console.error("Failed to raise dispute:", error); toast.error("Failed to raise dispute"); - } finally { - setIsDisputing(false); } };Also applies to: 74-90, 371-374, 396-412
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` at line 55, Remove the redundant isDisputing useState and its setters and instead rely on raiseDisputeMutation.isPending (v5 API) for pending state; specifically delete the const [isDisputing, setIsDisputing] declaration and remove all setIsDisputing(...) calls inside the dispute submit handlers, then replace any checks or disabled props that reference isDisputing with raiseDisputeMutation.isPending (apply the same changes to both CTAs/handlers that currently mirror this state). Ensure handlers (the dispute submit functions) no longer toggle local state and that the dialog/button disabled attributes use raiseDisputeMutation.isPending so there’s a single source of truth.hooks/use-dispute-mutations.ts (1)
38-55: Partial key invalidation may miss specific-bounty caches.
useBountyQueryusesqueryKey: ["Bounty", variables], and React Query matches invalidations as a prefix, soinvalidateQueries({ queryKey: ["Bounty"] })will correctly invalidate allBountyentries — good. However, the dispute review page usesqueryKey: ["dispute", disputeId](lowercase), so["dispute"]here works, but any future hook namedDispute/disputes(plural) won’t be caught. Worth standardizing the casing convention (matching the generated"Bounty"pattern) and documenting it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/use-dispute-mutations.ts` around lines 38 - 55, The invalidation keys are inconsistent in casing and plurality (e.g., "dispute" vs "disputes") which can miss caches; update the mutation hooks (useResolveDisputeMutation and the raise-dispute mutation) to use a standardized query-key convention (e.g., "Dispute" and "Disputes" to match "Bounty") or, better, replace literal strings with shared constants (e.g., QUERY_KEYS.Dispute / QUERY_KEYS.Disputes) and change the invalidateQueries calls to use those standardized keys (for example invalidateQueries({ queryKey: ["Dispute"] }) and invalidateQueries({ queryKey: ["Disputes"] }) so all related cached entries (including ["Dispute", id]) are correctly invalidated).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 376-377: The variable isFcfs (const isFcfs = bounty.type ===
"FIXED_PRICE") is unused in MobileCTA after the layout refactor—remove that dead
constant and any related unused imports or references (e.g., remove any leftover
conditional logic that checked isFcfs or refs to FcfsClaimButton in MobileCTA),
and ensure MobileCTA instead relies on canAct (bounty.status) and the current
unified button row; if FCFS behavior was intentionally removed, also delete or
unused-export FcfsClaimButton only where safe.
- Around line 97-102: The current canDispute boolean (defined in
bounty-detail-sidebar-cta.tsx) incorrectly includes "DISPUTED" in the allowed
statuses which makes desktop and mobile inconsistent; update the logic used for
both desktop (the check near line 178) and mobile (the check near line 439 and
the related block at 418-423) so that "DISPUTED" is removed from the allowed
statuses list and the button is hidden when bounty.status === "DISPUTED";
specifically, change the canDispute computation (and any equivalent guards) to
only allow ["SUBMITTED","UNDER_REVIEW","IN_PROGRESS"] AND (isCreator ||
isContributor) and ensure any extra explicit checks that previously excluded
"DISPUTED" are no longer needed so both desktop and mobile use the same
canDispute flag.
- Around line 74-90: The code calls a non-existent raiseDispute mutation (see
raiseDisputeMutation, MutationRaiseDisputeArgs, and RAISE_DISPUTE_MUTATION in
hooks/use-dispute-mutations.ts and usages in bounty-detail-sidebar-cta.tsx),
causing codegen and runtime GraphQL errors; fix by either adding a matching
raiseDispute mutation to the GraphQL schema.graphql with the expected args and
return type (then re-run codegen and adjust types), or remove the
RAISE_DISPUTE_MUTATION, MutationRaiseDisputeArgs import and all
raiseDisputeMutation usages (e.g., handleRaiseDispute in
bounty-detail-sidebar-cta.tsx) so the client uses the supported mutations
(addDisputeNote/assignDispute/escalateDispute/resolveDispute) instead.
In `@components/bounty-detail/page.tsx`:
- Around line 47-76: The AdminDisputeDetail GraphQL query (constructed via
useQuery with queryKey ["dispute", disputeId] and fetcher) must be gated by the
reviewer authorization: change the query's enabled condition to enabled:
!!disputeId && isReviewer so the request only fires for reviewers, and move the
pedestrian role check (the !isReviewer early-return) above any rendering
branches that check isLoading/isError/error so unauthorized viewers are
short-circuited before the query/loading/error handling runs; update the same
pattern for the other occurrence around lines 125-135.
- Around line 47-76: The query call using useQuery/fetcher currently uses a
loose any generic causing disputeData (and disputeData?.adminDisputeDetail) to
be untyped; replace the generic fetcher<any, { id: string }>(...) with the
concrete generated GraphQL type (e.g. fetcher<AdminDisputeDetailQuery, { id:
string }>(...)) or a local interface matching the query result so the response
and disputeData?.adminDisputeDetail are strongly typed, and update any
downstream usages accordingly; also fix the JSX react/no-unescaped-entities lint
error by replacing the literal double-quote characters in the JSX text at the
reported location with HTML entity " or use typographic quotes (e.g. “ ”)
to eliminate the unescaped-entities warning.
- Around line 97-101: The handler handleRequestAdditionalInfo currently only
shows a success toast and closes the dialog but never calls the backend; update
it to call the appropriate mutation or API (e.g., requestDisputeInfo or a
dispute-comment endpoint) passing additionalInfoRequest and relevant
bounty/claim id, await the response, show toast.success only on success and
toast.error on failure, and set loading state so the submit button is disabled
while awaiting; alternatively, if the endpoint isn't available yet, disable the
button or replace the success toast with a TODO/disabled flow and do not clear
additionalInfoRequest or close the dialog until the real mutation is wired up.
- Around line 195-200: The external GitHub PR anchor rendering (the JSX branch
that checks dispute.submission?.githubPullRequestUrl and renders the <a> with
target="_blank", FileText icon and "View on GitHub") must include rel="noopener
noreferrer" to prevent tabnabbing and referrer leakage; update that anchor so it
keeps its existing href, target and className but adds rel="noopener
noreferrer".
- Line 79: The code uses session?.user?.role in
components/bounty-detail/page.tsx to compute isReviewer but SessionUser
(lib/server-auth.ts) lacks a role field; update types or add a guard: either
augment the SessionUser interface to include role (e.g., "ADMIN" | "REVIEWER" |
...), ensure the session returned from your auth layer sets that field, and
update any types/imports used by page.tsx, or implement a typed guard function
(e.g., isAdminOrReviewer(user)) that safely checks user.role with proper type
narrowing and use that in place of direct access in the isReviewer assignment so
TypeScript strict mode no longer errors and role checks are reliable.
- Around line 81-95: The handleResolve function maps APPPROVED_CONTRIBUTOR to
the wrong enum (DISMISSED) which semantically means rejection; update the
mapping in handleResolve (the resolveDispute call) so APPPROVED_CONTRIBUTOR maps
to the correct DisputeResolutionEnum value (likely ApprovedWithConditions or
whatever enum means "contributor wins/funds released") and keep APPROVED_SPONSOR
→ FullRefund; confirm the correct enum name from the backend contract
(ApprovedWithConditions, FullRefund, etc.), replace the resolution string
accordingly, and ensure resolutionNotes still reflect the chosen resolution.
In `@hooks/use-dispute-mutations.ts`:
- Around line 11-22: The RAISE_DISPUTE_MUTATION constant is invalid: the GraphQL
schema has no raiseDispute mutation and AdminDisputeDto does not include
bountyId; update or remove it. Replace RAISE_DISPUTE_MUTATION with one of the
real mutations (assignDispute, escalateDispute, or resolveDispute) and request
the correct input/output shape from the schema (use AdminDisputeDto fields: id,
status, reason, description, createdAt, campaignId, milestoneId?, resolution?)
or remove the whole constant if dispute creation isn’t implemented; also remove
or update the nonexistent MutationRaiseDisputeArgs import to the correct
generated input type for the chosen mutation.
- Around line 34-55: The code references a non-existent "raiseDispute" mutation
and types; replace it with the correct schema mutation (likely
"escalateDispute") throughout: in use-dispute-mutations.ts
rename/useRaiseDisputeMutation to reflect the actual mutation, change the
generic type MutationRaiseDisputeArgs to the generated
MutationEscalateDisputeArgs (or the exact generated type for the intended
mutation), replace RAISE_DISPUTE_MUTATION with the correct
ESCALATE_DISPUTE_MUTATION (or actual exported query constant), and update any
imports for that query and types; also update the caller
(bounty-detail-sidebar-cta.tsx) to pass the argument shape matching the real
mutation signature (e.g., the fields expected by escalateDispute), and ensure
fetcher(RAISE_DISPUTE_MUTATION, ...) is using the corrected query constant so
the code compiles against the schema.
---
Outside diff comments:
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 425-476: The JSX return for the mobile CTA is malformed: remove
the duplicated primary Button and rewrite the conditional rendering so the outer
wrapper <div className="flex gap-2"> is always closed and each optional control
is a sibling; specifically, in the MobileCTA return (the block using canAct,
canDispute, canCancel, label, bounty.githubIssueUrl) keep one primary Button
that opens bounty.githubIssueUrl, then render the dispute Button when canDispute
(onClick -> setDisputeDialogOpen(true)), and render the cancel outline Button
when canCancel (onClick -> setCancelDialogOpen(true)), ensuring each conditional
returns a single element (no nested same-condition blocks) and all JSX tags are
properly balanced and closed.
---
Nitpick comments:
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 97-102: The expression computing canDispute uses a redundant "??
false" after an && chain; replace that with a boolean coercion to match the
mobile logic and avoid unnecessary nullish fallback. Locate the canDispute
assignment (references: canDispute, bounty.status, isCreator, isContributor) and
change it to return a strict boolean by wrapping the whole logical expression
with Boolean(...) (or prefixing with !!), or alternatively default isContributor
when it's produced (e.g., ensure isContributor is boolean by using ?? false
where it's defined); this yields the same behavior without the trailing "??
false".
- Line 55: Remove the redundant isDisputing useState and its setters and instead
rely on raiseDisputeMutation.isPending (v5 API) for pending state; specifically
delete the const [isDisputing, setIsDisputing] declaration and remove all
setIsDisputing(...) calls inside the dispute submit handlers, then replace any
checks or disabled props that reference isDisputing with
raiseDisputeMutation.isPending (apply the same changes to both CTAs/handlers
that currently mirror this state). Ensure handlers (the dispute submit
functions) no longer toggle local state and that the dialog/button disabled
attributes use raiseDisputeMutation.isPending so there’s a single source of
truth.
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Around line 150-162: The DISPUTED case in getStatusBadgeColor currently
returns "bg-yellow-100 text-yellow-900 border border-yellow-200" which adds an
extra border unlike the other statuses; update the DISPUTED branch in the
getStatusBadgeColor function to return only the background and text classes
(e.g., "bg-yellow-100 text-yellow-900") so it matches the visual style of
APPROVED/REJECTED/PENDING.
In `@components/bounty-detail/page.tsx`:
- Around line 112-123: In handleResolve, the catch block currently uses "catch
(error)" which shadows the outer "error" from the useQuery destructuring; rename
the catch parameter (e.g., to "err" or "resolveErr") and update any references
inside the catch so they use the new name, ensuring consistency with other
handlers (like the sidebar CTA) and avoiding confusion with the outer "error"
variable.
In `@hooks/use-dispute-mutations.ts`:
- Around line 38-55: The invalidation keys are inconsistent in casing and
plurality (e.g., "dispute" vs "disputes") which can miss caches; update the
mutation hooks (useResolveDisputeMutation and the raise-dispute mutation) to use
a standardized query-key convention (e.g., "Dispute" and "Disputes" to match
"Bounty") or, better, replace literal strings with shared constants (e.g.,
QUERY_KEYS.Dispute / QUERY_KEYS.Disputes) and change the invalidateQueries calls
to use those standardized keys (for example invalidateQueries({ queryKey:
["Dispute"] }) and invalidateQueries({ queryKey: ["Disputes"] }) so all related
cached entries (including ["Dispute", id]) are correctly invalidated).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 390efd25-d3f1-40cb-885f-49a7c273cdab
📒 Files selected for processing (5)
components/bounty-detail/bounty-detail-sidebar-cta.tsxcomponents/bounty-detail/bounty-detail-submissions-card.tsxcomponents/bounty-detail/page.tsxhooks/use-dispute-mutations.tstypes/participation.ts
Sync with main branch |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
components/bounty-detail/bounty-detail-sidebar-cta.tsx (3)
470-479:⚠️ Potential issue | 🟠 MajorRestore the mobile CTA branching by bounty type.
The button at Line 527 now always opens the GitHub issue, so mobile users can no longer claim fixed-price bounties or join competitions. The state derived at Line 470-479 is never used to drive the rendered action anymore.
Suggested direction
- <Button - className="flex-1 h-11 font-bold tracking-wide" - disabled={!canAct} - size="lg" - onClick={() => - canAct && - window.open(bounty.githubIssueUrl, "_blank", "noopener,noreferrer") - } - > - {label()} - </Button> + {isFcfs ? ( + <FcfsClaimButton bounty={bounty} /> + ) : isCompetition ? ( + hasJoined ? ( + <Button className="flex-1 h-11 font-bold tracking-wide" disabled size="lg"> + Joined ✓ + </Button> + ) : ( + <Button + className="flex-1 h-11 font-bold tracking-wide" + disabled={!canAct || isPastDeadline || joinMutation.isPending} + size="lg" + onClick={() => void handleJoin()} + > + {joinMutation.isPending ? ( + <Loader2 className="mr-2 size-4 animate-spin" /> + ) : ( + <Users className="mr-2 size-4" /> + )} + {canAct && !isPastDeadline ? "Join Competition" : label()} + </Button> + ) + ) : ( + <Button + className="flex-1 h-11 font-bold tracking-wide" + disabled={!canAct} + size="lg" + onClick={() => + canAct && + window.open(bounty.githubIssueUrl, "_blank", "noopener,noreferrer") + } + > + {label()} + </Button> + )}Also applies to: 526-537
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 470 - 479, The mobile CTA always opens the GitHub issue but should branch by bounty type using the existing state (canAct, isFcfs, isCompetition, canCancel) and competition helpers (hasJoined, isPastDeadline, joinMutation, handleJoin from useCompetitionJoinState). Update the CTA render/onclick where it currently calls the "open GitHub issue" action so that: if isFcfs and canAct it triggers the fixed-price claim flow (same handler used for desktop), if isCompetition it uses hasJoined/isPastDeadline to show join state and calls handleJoin or joinMutation appropriately, if canCancel shows cancel UI, otherwise falls back to opening the GitHub issue; reuse the existing handler functions and state variables (canAct, isFcfs, isCompetition, canCancel, handleJoin, joinMutation, hasJoined, isPastDeadline) to implement this branching.
4-12:⚠️ Potential issue | 🔴 CriticalAdd missing
Usersicon import.
Usersis rendered at lines 180, 196, and 230 but is not imported from lucide-react. This will cause a type-check failure.Suggested fix
import { Github, Copy, Check, AlertCircle, XCircle, Loader2, + Users, AlertTriangle, } from "lucide-react";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 4 - 12, The import list from "lucide-react" is missing the Users icon used in this module; update the top-level import that currently lists Github, Copy, Check, AlertCircle, XCircle, Loader2, AlertTriangle to also include Users so the JSX references to <Users /> (rendered in this component) resolve and TypeScript type-checks correctly.
548-575:⚠️ Potential issue | 🔴 CriticalFix the malformed
canCancelblock structure and remove the duplicated primary CTA.The code nests a second
{canCancel && (inside the first one and places a closing</div>tag within the conditional, which violates JSX rules and prevents compilation. Additionally, the primary button is rendered twice: once unconditionally at line 525–539, and again inside thecanCancelblock at lines 549–562, creating a duplicate CTA.Replace the
canCancelblock to render only the cancel button with the properdisabled={isCancelling}state:Suggested fix
- {canCancel && ( - <Button - className="flex-1 h-11 font-bold tracking-wide" - disabled={!canAct} - size="lg" - onClick={() => - canAct && - window.open( - bounty.githubIssueUrl, - "_blank", - "noopener,noreferrer", - ) - } - > - {label()} - </Button> - {canCancel && ( - <Button - variant="outline" - size="lg" - className="h-11 border-red-500/30 text-red-400 hover:bg-red-500/10 shrink-0" - onClick={() => setCancelDialogOpen(true)} - > - <XCircle className="size-4" /> - </Button> - )} - </div> - )} + {canCancel && ( + <Button + variant="outline" + size="lg" + className="h-11 border-red-500/30 text-red-400 hover:bg-red-500/10 shrink-0" + onClick={() => setCancelDialogOpen(true)} + disabled={isCancelling} + > + <XCircle className="size-4" /> + </Button> + )} + </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 548 - 575, The canCancel conditional is malformed and duplicates the primary CTA: remove the nested `{canCancel && (` that wraps the primary Button so the main primary CTA (the Button that uses label(), canAct and opens bounty.githubIssueUrl) is rendered only once, then render a separate `{canCancel && (...)}` block that only returns the cancel Button (variant="outline") with onClick={() => setCancelDialogOpen(true)} and disabled={isCancelling}; also remove the stray `</div>` currently placed inside the conditional so JSX is balanced. Ensure you keep references to Button, label(), canAct, bounty.githubIssueUrl, canCancel, setCancelDialogOpen, isCancelling and XCircle when fixing the JSX structure in bounty-detail-sidebar-cta.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 470-479: The mobile CTA always opens the GitHub issue but should
branch by bounty type using the existing state (canAct, isFcfs, isCompetition,
canCancel) and competition helpers (hasJoined, isPastDeadline, joinMutation,
handleJoin from useCompetitionJoinState). Update the CTA render/onclick where it
currently calls the "open GitHub issue" action so that: if isFcfs and canAct it
triggers the fixed-price claim flow (same handler used for desktop), if
isCompetition it uses hasJoined/isPastDeadline to show join state and calls
handleJoin or joinMutation appropriately, if canCancel shows cancel UI,
otherwise falls back to opening the GitHub issue; reuse the existing handler
functions and state variables (canAct, isFcfs, isCompetition, canCancel,
handleJoin, joinMutation, hasJoined, isPastDeadline) to implement this
branching.
- Around line 4-12: The import list from "lucide-react" is missing the Users
icon used in this module; update the top-level import that currently lists
Github, Copy, Check, AlertCircle, XCircle, Loader2, AlertTriangle to also
include Users so the JSX references to <Users /> (rendered in this component)
resolve and TypeScript type-checks correctly.
- Around line 548-575: The canCancel conditional is malformed and duplicates the
primary CTA: remove the nested `{canCancel && (` that wraps the primary Button
so the main primary CTA (the Button that uses label(), canAct and opens
bounty.githubIssueUrl) is rendered only once, then render a separate `{canCancel
&& (...)}` block that only returns the cancel Button (variant="outline") with
onClick={() => setCancelDialogOpen(true)} and disabled={isCancelling}; also
remove the stray `</div>` currently placed inside the conditional so JSX is
balanced. Ensure you keep references to Button, label(), canAct,
bounty.githubIssueUrl, canCancel, setCancelDialogOpen, isCancelling and XCircle
when fixing the JSX structure in bounty-detail-sidebar-cta.tsx.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 21607fef-d087-486e-bed5-30f1e0f2644c
📒 Files selected for processing (1)
components/bounty-detail/bounty-detail-sidebar-cta.tsx
Issue resolved |
Benjtalkshow
left a comment
There was a problem hiding this comment.
You’ve pushed two merges from main (63133f9, a98df30) and manually marked coderabbit as resolved, but there haven’t been any actual fixes since the original review two days ago. None of the requested changes have been addressed, and the latest merge from main made things worse. In bounty-detail-sidebar-cta.tsx (lines 548–575), there’s now duplicated JSX with mismatched parentheses, which is breaking the parser.
CI build and lint are still failing. Running pnpm build gives four errors:
bounty-detail-sidebar-cta.tsx:564—')' expectedbounty-detail-sidebar-cta.tsx:575— unexpected tokenpage.tsx:49—anytype not allowedpage.tsx:222— unescaped quotes (x2)
Please go through and fix all the requested changes. If there isn’t real progress on this PR soon, it will be unassigned and closed.
Closes #134
Summary
This PR implements the decentralized dispute resolution flow for handling disagreements between sponsors and contributors on bounties.
Changes Made
Added DISPUTED status to both Application and Submission types
Ensured seamless integration with existing status workflows
Added role-gated "Raise Dispute" button for active participants (contributor/sponsor involved)
Button triggers dispute creation workflow with optional reason/details prompt
Visibility restricted to eligible users only
Created dedicated arbitration interface for reviewers
Displays both sides: contributor submission/application and sponsor feedback/context
Provides reviewer actions:
Approve contributor
Approve sponsor
Request additional info (optional)
Tracks final decisions and updates relevant entity statuses
Implemented with accessible, responsive design
Features Implemented
Dispute status propagation to applications and submissions
Permission-based dispute initiation (only involved parties)
Comprehensive reviewer interface with complete context
Status updates upon resolution
Accessible and responsive UI for all user types
Testing Performed
Verified Disputed status reflects correctly on applications/submissions
Confirmed eligible users can raise disputes from bounty page
Validated reviewer page shows complete dispute context
Tested dispute resolution updates all affected entities
Checked permissions: only authorized users can raise/review disputes
Ensured accessible, responsive design across device sizes
Additional Considerations
Workflow designed for extensibility to future dispute types/mediation steps
Dispute events logged for audit and transparency
Notifications considered for dispute lifecycle events (raised/resolved)
This implementation improves trust and transparency by providing a formal mechanism for resolving disagreements while maintaining clear audit trails and role-based access controls.
Summary by CodeRabbit
New Features
Bug Fixes / UI
Chores