feat: implement raise dispute flow with redirect to dispute review page#256
Conversation
- Add useRaiseDispute mutation hook in hooks/use-bounty-application.ts
that POSTs to /api/disputes with bountyId, reason, and description,
then invalidates the bounty detail and list queries on success
- Create app/api/disputes/route.ts Next.js API route handler that
authenticates the user, validates input, and proxies the request
to the backend REST endpoint with the auth token
- Enable the Raise Dispute button in bounty-detail-sidebar-cta.tsx
(removes disabled + Coming Soon label), wires up an AlertDialog
with a reason Select (DisputeReasonEnum) and description Textarea,
inline validation that keeps the dialog open on empty fields, a
loading spinner during submission, and on success closes the dialog,
shows a toast, and redirects to /dispute/{newDisputeId}
- Document the RaiseDispute operation in admin-dispute.graphql
|
@od-hunter is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Review limit reached
More reviews will be available in 58 minutes and 44 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR implements a complete "Raise a Dispute" feature. A new ChangesRaise Dispute Flow
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
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 |
|
@od-hunter 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! 🚀 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/api/disputes/route.ts`:
- Around line 66-77: The fetch call that posts to `${backendUrl}/disputes` in
route.ts needs an AbortController-based timeout to avoid hanging; create an
AbortController, pass controller.signal into the fetch options for the
backendResponse call, start a setTimeout (e.g. 5–10s) that calls
controller.abort(), and clear the timeout after fetch completes (use
try/finally). Ensure you handle the abort/AbortError (from fetch of
backendResponse) and return an appropriate 504/timeout response instead of
leaving the request open.
- Around line 41-46: Validate the incoming reason against the DisputeReasonEnum
instead of accepting any string: in the POST handler in route.ts (where reason
is checked) replace the loose typeof check with a membership check against
DisputeReasonEnum (or a helper like isValidDisputeReason) and return
NextResponse.json({ error: "invalid reason", valid:
Object.values(DisputeReasonEnum) }, { status: 400 }) when it does not match so
invalid dispute reasons are rejected early before forwarding downstream.
🪄 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: ee8e4a10-c726-408b-a207-2423c2ca8a8e
📒 Files selected for processing (4)
app/api/disputes/route.tscomponents/bounty-detail/bounty-detail-sidebar-cta.tsxhooks/use-bounty-application.tslib/graphql/operations/admin-dispute.graphql
| if (!reason || typeof reason !== "string") { | ||
| return NextResponse.json( | ||
| { error: "reason is required" }, | ||
| { status: 400 }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Reject values outside DisputeReasonEnum.
This currently accepts any string and forwards it downstream, so invalid dispute reasons bypass server-side validation and fail later with a generic backend error.
Suggested fix
-import type { AdminDisputeDto, DisputeReasonEnum } from "`@/lib/graphql/generated`";
+import { DisputeReasonEnum } from "`@/lib/graphql/generated`";
+import type { AdminDisputeDto } from "`@/lib/graphql/generated`";
...
- if (!reason || typeof reason !== "string") {
+ if (
+ !reason ||
+ typeof reason !== "string" ||
+ !Object.values(DisputeReasonEnum).includes(reason as DisputeReasonEnum)
+ ) {
return NextResponse.json(
- { error: "reason is required" },
+ { error: "reason must be a valid dispute reason" },
{ status: 400 },
);
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/api/disputes/route.ts` around lines 41 - 46, Validate the incoming reason
against the DisputeReasonEnum instead of accepting any string: in the POST
handler in route.ts (where reason is checked) replace the loose typeof check
with a membership check against DisputeReasonEnum (or a helper like
isValidDisputeReason) and return NextResponse.json({ error: "invalid reason",
valid: Object.values(DisputeReasonEnum) }, { status: 400 }) when it does not
match so invalid dispute reasons are rejected early before forwarding
downstream.
| const backendResponse = await fetch(`${backendUrl}/disputes`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| ...(token ? { Authorization: `Bearer ${token}` } : {}), | ||
| }, | ||
| body: JSON.stringify({ | ||
| campaignId, | ||
| reason, | ||
| description, | ||
| }), | ||
| }); |
There was a problem hiding this comment.
Add a timeout to the backend proxy call.
The route waits on fetch() with no abort/timeout, so a slow or wedged backend can pin this request until the platform times it out.
Suggested fix
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 10_000);
+
- const backendResponse = await fetch(`${backendUrl}/disputes`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- ...(token ? { Authorization: `Bearer ${token}` } : {}),
- },
- body: JSON.stringify({
- campaignId,
- reason,
- description,
- }),
- });
+ let backendResponse: Response;
+ try {
+ backendResponse = await fetch(`${backendUrl}/disputes`, {
+ method: "POST",
+ signal: controller.signal,
+ headers: {
+ "Content-Type": "application/json",
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
+ },
+ body: JSON.stringify({
+ campaignId,
+ reason,
+ description,
+ }),
+ });
+ } finally {
+ clearTimeout(timeout);
+ }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/api/disputes/route.ts` around lines 66 - 77, The fetch call that posts to
`${backendUrl}/disputes` in route.ts needs an AbortController-based timeout to
avoid hanging; create an AbortController, pass controller.signal into the fetch
options for the backendResponse call, start a setTimeout (e.g. 5–10s) that calls
controller.abort(), and clear the timeout after fetch completes (use
try/finally). Ensure you handle the abort/AbortError (from fetch of
backendResponse) and return an appropriate 504/timeout response instead of
leaving the request open.
Benjtalkshow
left a comment
There was a problem hiding this comment.
The dispute flow itself is wired correctly: useRaiseDispute hook, REST proxy with auth check and validation, dialog with reason select and description textarea, inline validation, success path that closes the dialog and redirects to /dispute/{id}. All four acceptance criteria pass functionally.
Three things to fix before merge.
app/api/disputes/route.ts:3 imports graphqlRequest but never uses it. The PR uses REST since the GraphQL mutation isn't available yet. Remove the dead import.
Same file, line 63: const { getAccessToken } = await import("@/lib/auth-utils"); is a dynamic import inside the handler. Move it to a regular top-of-file import.
The biggest issue: this adds 176 lines inline to bounty-detail-sidebar-cta.tsx, growing it from around 387 to 563 lines. Issue #209 has a separate open PR (Biokes) trying to split this file because it's already too big. Please extract a RaiseDisputeDialog component into its own file. The sidebar only needs the trigger button and the disputeDialogOpen state. Move the dialog markup, DISPUTE_REASON_LABELS, validation, mutation wiring, and reset-on-close logic into the new component.
Once those are in this is ready.
|
@od-hunter |
Extract RaiseDisputeDialog into its own component and clean up the disputes API route imports per maintainer review.
|
Hi @Benjtalkshow , please review |
PR #256 introduced raise-dispute-dialog.tsx which imports useRaiseDispute from hooks/use-bounty-application.ts, but a merge with main during that PR dropped the hook itself. tsc --noEmit failed on main after merge: raise-dispute-dialog.tsx:28: error TS2305: Module '@/hooks/use-bounty-application' has no exported member 'useRaiseDispute'. Re-adding the hook with the same shape as the original commit.
Closes #203
#203
Summary by CodeRabbit