From 2d2b2e9f2d740d1be1f4a72f1479bedab5e4e43c Mon Sep 17 00:00:00 2001 From: BWM0223 Date: Wed, 24 Jun 2026 21:14:11 -0700 Subject: [PATCH] feat: add dispute resolution UI component --- components/disputes/DisputeResolution.tsx | 218 ++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 components/disputes/DisputeResolution.tsx diff --git a/components/disputes/DisputeResolution.tsx b/components/disputes/DisputeResolution.tsx new file mode 100644 index 000000000..093d6ed47 --- /dev/null +++ b/components/disputes/DisputeResolution.tsx @@ -0,0 +1,218 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Textarea } from "@/components/ui/textarea"; +import { AlertTriangle, CheckCircle, XCircle, Clock, MessageSquare } from "lucide-react"; + +interface Dispute { + id: string; + bountyId: string; + participantId: string; + participantName: string; + submissionId: string; + reason: string; + status: "open" | "under_review" | "upheld" | "overturned"; + createdAt: Date; + resolvedAt?: Date; + resolution?: string; + resolvedBy?: string; +} + +interface DisputeResolutionProps { + disputes: Dispute[]; + isOrganizer: boolean; + onRaiseDispute?: (reason: string) => Promise; + onResolveDispute?: (disputeId: string, decision: "uphold" | "overturn", notes: string) => Promise; +} + +export function DisputeResolution({ + disputes, + isOrganizer, + onRaiseDispute, + onResolveDispute, +}: DisputeResolutionProps) { + const [newDisputeReason, setNewDisputeReason] = useState(""); + const [resolutionNotes, setResolutionNotes] = useState>({}); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleRaiseDispute = async () => { + if (!newDisputeReason.trim() || !onRaiseDispute) return; + setIsSubmitting(true); + try { + await onRaiseDispute(newDisputeReason); + setNewDisputeReason(""); + } finally { + setIsSubmitting(false); + } + }; + + const handleResolve = async (disputeId: string, decision: "uphold" | "overturn") => { + if (!onResolveDispute) return; + setIsSubmitting(true); + try { + await onResolveDispute(disputeId, decision, resolutionNotes[disputeId] || ""); + } finally { + setIsSubmitting(false); + } + }; + + const getStatusBadge = (status: Dispute["status"]) => { + const config = { + open: { variant: "destructive" as const, icon: AlertTriangle, label: "Open" }, + under_review: { variant: "secondary" as const, icon: Clock, label: "Under Review" }, + upheld: { variant: "default" as const, icon: CheckCircle, label: "Upheld" }, + overturned: { variant: "outline" as const, icon: XCircle, label: "Overturned" }, + }; + const { variant, icon: Icon, label } = config[status]; + return ( + + + {label} + + ); + }; + + const openDisputes = disputes.filter((d) => d.status === "open" || d.status === "under_review"); + const resolvedDisputes = disputes.filter((d) => d.status === "upheld" || d.status === "overturned"); + + return ( +
+ {/* Participant: Raise Dispute */} + {!isOrganizer && onRaiseDispute && ( + + + + + Raise a Dispute + + + If you believe there was an error in the evaluation of your submission, you can raise a dispute. + + + +