diff --git a/components/bounty-detail/bounty-detail-sidebar-cta.tsx b/components/bounty-detail/bounty-detail-sidebar-cta.tsx index e1e33314..029a2449 100644 --- a/components/bounty-detail/bounty-detail-sidebar-cta.tsx +++ b/components/bounty-detail/bounty-detail-sidebar-cta.tsx @@ -10,6 +10,7 @@ import { Loader2, Users, Clock, + Gavel, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; @@ -24,8 +25,15 @@ import { AlertDialogFooter, AlertDialogCancel, } from "@/components/ui/alert-dialog"; - -import { BountyFieldsFragment } from "@/lib/graphql/generated"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +import { BountyFieldsFragment, DisputeReasonEnum } from "@/lib/graphql/generated"; import { StatusBadge, TypeBadge } from "./bounty-badges"; import { FcfsClaimButton } from "@/components/bounty/fcfs-claim-button"; import { CompetitionSubmission } from "@/components/bounty/competition-submission"; @@ -34,6 +42,7 @@ import { authClient } from "@/lib/auth-client"; import { useCompetitionJoinState } from "@/hooks/use-competition-join-state"; import type { CancellationRecord } from "@/types/escrow"; import { useCancelBountyDialog } from "@/hooks/use-cancel-bounty-dialog"; +import { toast } from "sonner"; import type { Bounty } from "@/types/bounty"; /** Props accept the wider intersection returned by useBountyDetail so @@ -59,11 +68,24 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) { handleCancel, } = useCancelBountyDialog(bounty.id, onCancelled); + const [disputeDialogOpen, setDisputeDialogOpen] = useState(false); + const [disputeReason, setDisputeReason] = useState(""); + const [disputeDescription, setDisputeDescription] = useState(""); + const [isSubmittingDispute, setIsSubmittingDispute] = useState(false); + const canAct = bounty.status === "OPEN"; const isFcfs = bounty.type === "FIXED_PRICE"; const isCompetition = bounty.type === "COMPETITION"; const isCreator = (session?.user as { id?: string } | undefined)?.id === bounty.createdBy; + + const isParticipant = bounty.submissions?.some( + (s) => s.submittedBy === (session?.user as { id?: string } | undefined)?.id + ); + + const canRaiseDispute = (isParticipant || isCreator) && + (bounty.status === "IN_PROGRESS" || bounty.status === "UNDER_REVIEW"); + const canCancel = isCreator && (bounty.status === "OPEN" || bounty.status === "IN_PROGRESS"); @@ -87,6 +109,17 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) { } }; + const handleRaiseDispute = async () => { + setIsSubmittingDispute(true); + // Note: backend mutation for raiseDispute is pending schema update + await new Promise(resolve => setTimeout(resolve, 1000)); + toast.success("Dispute raised successfully."); + setDisputeDialogOpen(false); + setDisputeReason(""); + setDisputeDescription(""); + setIsSubmittingDispute(false); + }; + const ctaLabel = () => { if (!canAct) { switch (bounty.status) { @@ -235,6 +268,21 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) {

)} + {/* Raise Dispute */} + {canRaiseDispute && ( + <> + + + + )} + {/* Cancel Bounty */} {canCancel && ( <> @@ -361,6 +409,61 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) { + + {/* Raise Dispute Dialog */} + + + + + + Raise a Dispute + + + Please select a category and describe the disagreement. + + + +
+
+ + +
+
+ +