Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion components/bounty-detail/bounty-detail-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,28 @@ export function BountyDetailClient({ bountyId }: { bountyId: string }) {
{bounty.type === "MILESTONE_BASED" &&
isAssignedApplicant &&
walletAddress &&
bounty.status === "IN_PROGRESS" && (
(() => {
const mySubmission = bounty.submissions?.find(
(s) => s.submittedBy === session?.user?.id,
);
const hasRevision =
mySubmission?.status === "REVISION_REQUESTED" &&
!!mySubmission?.reviewComments;
return (
bounty.status === "IN_PROGRESS" ||
(bounty.status === "UNDER_REVIEW" && hasRevision)
);
})() && (
<ApplicationSubmitWorkPanel
bountyId={bountyId}
contributorAddress={walletAddress}
revisionFeedback={
bounty.submissions?.find(
(s) =>
s.submittedBy === session?.user?.id &&
s.status === "REVISION_REQUESTED",
)?.reviewComments ?? undefined
}
/>
)}

Expand All @@ -256,6 +274,7 @@ export function BountyDetailClient({ bountyId }: { bountyId: string }) {
<SubmissionApprovalPanel
bounty={bounty}
creatorAddress={walletAddress}
submissionId={bounty.submissions?.[0]?.id}
submittedWorkCid={
bounty.submissions?.[0]?.githubPullRequestUrl || undefined
}
Expand Down
24 changes: 23 additions & 1 deletion components/bounty/application-submit-work-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"use client";

import { useState } from "react";
import { Upload, Link as LinkIcon, Send } from "lucide-react";
import {
Upload,
Link as LinkIcon,
Send,
MessageSquareWarning,
} from "lucide-react";
import {
Card,
CardContent,
Expand All @@ -17,11 +22,13 @@ import { useSubmitApplicationWork } from "@/hooks/use-bounty-application";
interface ApplicationSubmitWorkPanelProps {
bountyId: string;
contributorAddress: string;
revisionFeedback?: string;
}

export function ApplicationSubmitWorkPanel({
bountyId,
contributorAddress,
revisionFeedback,
}: ApplicationSubmitWorkPanelProps) {
const [workCid, setWorkCid] = useState("");

Expand Down Expand Up @@ -51,6 +58,21 @@ export function ApplicationSubmitWorkPanel({
</CardDescription>
</CardHeader>
<CardContent className="pt-6">
{revisionFeedback && (
<div className="mb-6 rounded-lg border border-amber-500/30 bg-amber-500/10 p-4">
<div className="flex items-start gap-3">
<MessageSquareWarning className="size-5 text-amber-400 mt-0.5 shrink-0" />
<div>
<p className="text-sm font-semibold text-amber-300 mb-1">
Revision Requested
</p>
<p className="text-sm text-amber-200/80 whitespace-pre-wrap leading-relaxed">
{revisionFeedback}
</p>
</div>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="work-cid">
Expand Down
120 changes: 101 additions & 19 deletions components/bounty/submission-approval-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AlertTriangle,
ExternalLink,
ShieldCheck,
RotateCcw,
} from "lucide-react";
import {
Card,
Expand All @@ -18,7 +19,12 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { useApproveApplicationSubmission } from "@/hooks/use-bounty-application";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "sonner";
import {
useApproveApplicationSubmission,
useRequestRevisions,
} from "@/hooks/use-bounty-application";
import type { BountyFieldsFragment } from "@/lib/graphql/generated";
import type { Bounty } from "@/types/bounty";

Expand All @@ -29,19 +35,26 @@ interface SubmissionApprovalPanelProps {
creatorAddress: string;
submittedWorkCid?: string;
submissionDescription?: string;
submissionId?: string;
}

export function SubmissionApprovalPanel({
bounty,
creatorAddress,
submittedWorkCid,
submissionDescription,
submissionId,
}: SubmissionApprovalPanelProps) {
const [points, setPoints] = useState<number>(5);
const [showRevisionForm, setShowRevisionForm] = useState(false);
const [revisionFeedback, setRevisionFeedback] = useState("");

const { mutate: approveSubmission, isPending: isApproving } =
useApproveApplicationSubmission();

const { mutate: requestRevisions, isPending: isRequestingRevisions } =
useRequestRevisions();

const handleApprove = () => {
const clampedPoints = Math.max(1, Math.min(100, points || 0));
approveSubmission({
Expand All @@ -51,6 +64,27 @@ export function SubmissionApprovalPanel({
});
};

const handleRequestRevisions = () => {
if (!submissionId || !revisionFeedback.trim()) return;
requestRevisions(
{
bountyId: bounty.id,
submissionId,
feedback: revisionFeedback.trim(),
},
{
onSuccess: () => {
toast.success("Revision request sent to contributor.");
setShowRevisionForm(false);
setRevisionFeedback("");
},
onError: () => {
toast.error("Failed to request revisions. Please try again.");
},
},
);
};

return (
<Card className="border-emerald-500/20 bg-emerald-500/5 backdrop-blur-sm overflow-hidden">
<CardHeader className="border-b border-emerald-500/10 pb-4">
Expand Down Expand Up @@ -131,25 +165,73 @@ export function SubmissionApprovalPanel({
</Button>
</div>

{/* Revision Section - Coming Soon */}
{/* Revision Section */}
<div className="space-y-4 border-l border-gray-800/50 pl-6">
<div className="h-full flex flex-col justify-center items-center text-center p-4 border border-dashed border-gray-800 rounded-lg opacity-60">
<AlertTriangle className="size-6 text-gray-500 mb-2" />
<h4 className="text-sm font-medium text-gray-400 mb-1">
Needs Changes?
</h4>
<p className="text-xs text-gray-500 mb-4">
Request revisions before releasing the escrow.
</p>
<Button
variant="outline"
size="sm"
className="border-gray-700 text-gray-500 cursor-not-allowed"
disabled
>
Coming Soon
</Button>
</div>
{!showRevisionForm ? (
<div className="h-full flex flex-col justify-center items-center text-center p-4 border border-dashed border-gray-800 rounded-lg">
<AlertTriangle className="size-6 text-amber-500 mb-2" />
<h4 className="text-sm font-medium text-gray-300 mb-1">
Needs Changes?
</h4>
<p className="text-xs text-gray-500 mb-4">
Request revisions before releasing the escrow.
</p>
<Button
variant="outline"
size="sm"
className="border-amber-700/50 text-amber-400 hover:bg-amber-500/10 hover:border-amber-600"
onClick={() => setShowRevisionForm(true)}
disabled={!submissionId}
>
<RotateCcw className="size-3 mr-1.5" />
Request Revisions
</Button>
</div>
) : (
<div className="space-y-3">
<div>
<Label
htmlFor="revision-feedback"
className="text-sm font-medium text-gray-300"
>
Revision Feedback
</Label>
<p className="text-xs text-gray-500 mt-1 mb-2">
Describe what needs to change before you can approve.
</p>
<Textarea
id="revision-feedback"
placeholder="Please update the API documentation to include error codes, and add unit tests for the authentication flow..."
value={revisionFeedback}
onChange={(e) => setRevisionFeedback(e.target.value)}
className="bg-gray-900/50 border-gray-700 resize-none min-h-[100px]"
/>
</div>
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
className="flex-1 border-gray-700 text-gray-400"
onClick={() => {
setShowRevisionForm(false);
setRevisionFeedback("");
}}
disabled={isRequestingRevisions}
>
Cancel
</Button>
<Button
size="sm"
className="flex-1 bg-amber-500 hover:bg-amber-600 text-white"
onClick={handleRequestRevisions}
disabled={!revisionFeedback.trim() || isRequestingRevisions}
>
<RotateCcw className="size-3 mr-1.5" />
{isRequestingRevisions ? "Sending..." : "Send Request"}
</Button>
</div>
</div>
)}
</div>
</div>
</CardContent>
Expand Down
Loading
Loading