From a8e4408041f925bb72360e1025878df6fafc84bd Mon Sep 17 00:00:00 2001 From: frank0277 <286547318+frank0277@users.noreply.github.com> Date: Fri, 29 May 2026 04:35:55 +0100 Subject: [PATCH 1/3] fix: implement decline applicant flow --- .../bounty/application-review-dashboard.tsx | 129 ++++++++++++++++-- hooks/use-bounty-application.ts | 91 ++++++++++++ 2 files changed, 211 insertions(+), 9 deletions(-) diff --git a/components/bounty/application-review-dashboard.tsx b/components/bounty/application-review-dashboard.tsx index 2fdd0d16..0cafb9dc 100644 --- a/components/bounty/application-review-dashboard.tsx +++ b/components/bounty/application-review-dashboard.tsx @@ -1,6 +1,5 @@ "use client"; - -import { useState } from "react"; +import { useMemo, useState } from "react"; import { Users, CheckCircle, @@ -8,12 +7,27 @@ import { Star, Trophy, ArrowRight, + XCircle, } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; - -import { useSelectApplicant } from "@/hooks/use-bounty-application"; +import { Textarea } from "@/components/ui/textarea"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { toast } from "sonner"; +import { + useDeclineApplicant, + useSelectApplicant, +} from "@/hooks/use-bounty-application"; export interface Application { id: string; @@ -45,9 +59,14 @@ export function ApplicationReviewDashboard({ applications, }: ApplicationReviewDashboardProps) { const [selectedForCompare, setSelectedForCompare] = useState([]); + const [reviewApplications, setReviewApplications] = useState(applications); + const [declineTarget, setDeclineTarget] = useState(null); + const [declineReason, setDeclineReason] = useState(""); const { mutate: selectApplicant, isPending: isSelecting } = useSelectApplicant(); - + useEffect(() => { + setReviewApplications(applications); + }, [applications]); const handleSelectApplicant = (applicantAddress: string) => { selectApplicant({ bountyId, @@ -56,6 +75,44 @@ export function ApplicationReviewDashboard({ }); }; + const { mutate: declineApplicant, isPending: isDeclining } = + useDeclineApplicant(); + + const handleDeclineApplicant = () => { + if (!declineTarget) return; + + const previousApplications = reviewApplications; + const applicantAddress = declineTarget.applicantAddress; + const reason = declineReason.trim(); + + setReviewApplications((current) => + current.filter((app) => app.applicantAddress !== applicantAddress), + ); + + setSelectedForCompare((current) => + current.filter((id) => id !== declineTarget.id), + ); + + declineApplicant( + { + bountyId, + applicantAddress, + reason, + }, + { + onSuccess: () => { + toast.success("Applicant declined"); + setDeclineTarget(null); + setDeclineReason(""); + }, + onError: () => { + setReviewApplications(previousApplications); + toast.error("Failed to decline applicant"); + }, + }, + ); + }; + const visibleApplications = reviewApplications; const toggleCompare = (id: string) => { if (selectedForCompare.includes(id)) { setSelectedForCompare(selectedForCompare.filter((i) => i !== id)); @@ -113,6 +170,19 @@ export function ApplicationReviewDashboard({ {selectedForCompare.includes(app.id) ? "Comparing" : "Compare"} )} +
- {applications + {visibleApplications .filter((app) => selectedForCompare.includes(app.id)) .map((app) => renderApplicationCard(app, false))}
) : (
- {applications.map((app) => renderApplicationCard(app, false))} + {visibleApplications.map((app) => renderApplicationCard(app, false))}
)} + { + if (!open) { + setDeclineTarget(null); + setDeclineReason(""); + } + }} + > + + + Decline applicant? + + This will remove the applicant from the review queue. You can + include an optional reason for internal records. + + + +