From 4f220cbd272689929113fe6fd35cd95486e8f908 Mon Sep 17 00:00:00 2001 From: Jopsan-gm Date: Sat, 30 May 2026 11:49:43 -0600 Subject: [PATCH 1/7] feat: implement maintainer dashboard actions for model 4 bounties (#205) --- .../bounty-detail/bounty-detail-client.tsx | 1 + .../model4-maintainer-dashboard.tsx | 205 ++++++++++++++---- hooks/use-bounty-application.ts | 137 ++++++++++++ 3 files changed, 302 insertions(+), 41 deletions(-) create mode 100644 hooks/use-bounty-application.ts diff --git a/components/bounty-detail/bounty-detail-client.tsx b/components/bounty-detail/bounty-detail-client.tsx index fed32e8c..f145838a 100644 --- a/components/bounty-detail/bounty-detail-client.tsx +++ b/components/bounty-detail/bounty-detail-client.tsx @@ -177,6 +177,7 @@ export function BountyDetailClient({ bountyId }: { bountyId: string }) { getFullMilestoneData(bounty); return ( diff --git a/components/bounty-detail/model4-maintainer-dashboard.tsx b/components/bounty-detail/model4-maintainer-dashboard.tsx index 52bc6f74..24d71d5b 100644 --- a/components/bounty-detail/model4-maintainer-dashboard.tsx +++ b/components/bounty-detail/model4-maintainer-dashboard.tsx @@ -12,6 +12,17 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Textarea } from "@/components/ui/textarea"; +import { toast } from "sonner"; +import { useBountyApplication } from "@/hooks/use-bounty-application"; import { ChevronRight, UserMinus, @@ -23,6 +34,7 @@ import { } from "lucide-react"; interface Model4MaintainerDashboardProps { + bountyId: string; milestones: Milestone[]; contributors: ContributorProgress[]; maxSlots?: number; @@ -30,18 +42,76 @@ interface Model4MaintainerDashboardProps { } export function Model4MaintainerDashboard({ + bountyId, milestones, contributors: initialContributors, maxSlots = 5, className, }: Model4MaintainerDashboardProps) { - const [loadingAction, setLoadingAction] = React.useState(null); + const { releasePayment, advanceContributor, removeContributor, sendMessage } = + useBountyApplication(bountyId); + + const [selectedContributor, setSelectedContributor] = + React.useState(null); + const [isSubmissionsOpen, setIsSubmissionsOpen] = React.useState(false); + const [isMessageOpen, setIsMessageOpen] = React.useState(false); + const [messageText, setMessageText] = React.useState(""); + + const handleReleasePayment = (contributor: ContributorProgress) => { + releasePayment.mutate( + { + contributorId: contributor.userId, + milestoneId: contributor.currentMilestoneId, + }, + { + onSuccess: () => + toast.success(`Payment released for ${contributor.userName}`), + }, + ); + }; + + const handleAdvance = (contributor: ContributorProgress) => { + advanceContributor.mutate( + { contributorId: contributor.userId }, + { + onSuccess: () => + toast.success(`${contributor.userName} advanced to next milestone`), + }, + ); + }; + + const handleRemove = (contributor: ContributorProgress) => { + removeContributor.mutate( + { contributorId: contributor.userId }, + { + onSuccess: () => + toast.success(`${contributor.userName} removed from bounty`), + }, + ); + }; + + const handleOpenSubmissions = (contributor: ContributorProgress) => { + setSelectedContributor(contributor); + setIsSubmissionsOpen(true); + }; - const handleAction = async (action: string, userName: string) => { - setLoadingAction(`${action}-${userName}`); - console.log(`[Coming soon] ${action} for ${userName}`); - await new Promise((r) => setTimeout(r, 1000)); - setLoadingAction(null); + const handleOpenMessage = (contributor: ContributorProgress) => { + setSelectedContributor(contributor); + setMessageText(""); + setIsMessageOpen(true); + }; + + const handleSendMessage = () => { + if (!selectedContributor || !messageText.trim()) return; + sendMessage.mutate( + { contributorId: selectedContributor.userId, message: messageText }, + { + onSuccess: () => { + toast.success(`Message sent to ${selectedContributor.userName}`); + setIsMessageOpen(false); + }, + }, + ); }; return ( @@ -135,17 +205,12 @@ export function Model4MaintainerDashboard({ variant="ghost" size="icon-sm" className="text-gray-400 hover:text-white" - onClick={() => - handleAction("Message", contributor.userName) - } - disabled={loadingAction !== null} + onClick={() => handleOpenMessage(contributor)} > - - Send Message [Coming soon] - + Send Message @@ -154,15 +219,9 @@ export function Model4MaintainerDashboard({ variant="outline" size="sm" className="h-8 text-xs border-gray-700 hover:bg-gray-800" - onClick={() => - handleAction( - "View Submissions", - contributor.userName, - ) - } - disabled={loadingAction !== null} + onClick={() => handleOpenSubmissions(contributor)} > - View Submissions [Coming soon] + View Submissions Review work @@ -173,21 +232,21 @@ export function Model4MaintainerDashboard({ Pay for milestone @@ -199,18 +258,20 @@ export function Model4MaintainerDashboard({ size="sm" variant="secondary" className="h-8 text-xs font-bold" - onClick={() => - handleAction("Advance", contributor.userName) + onClick={() => handleAdvance(contributor)} + disabled={ + advanceContributor.isPending && + advanceContributor.variables?.contributorId === + contributor.userId } - disabled={loadingAction !== null} > - {loadingAction === - `Advance-${contributor.userName}` ? ( + {advanceContributor.isPending && + advanceContributor.variables?.contributorId === + contributor.userId ? ( ) : ( <> - Advance [Coming soon]{" "} - + Advance )} @@ -224,12 +285,20 @@ export function Model4MaintainerDashboard({ variant="ghost" size="icon-sm" className="text-red-400/50 hover:text-red-400 hover:bg-red-400/10" - onClick={() => - handleAction("Remove", contributor.userName) + onClick={() => handleRemove(contributor)} + disabled={ + removeContributor.isPending && + removeContributor.variables?.contributorId === + contributor.userId } - disabled={loadingAction !== null} > - + {removeContributor.isPending && + removeContributor.variables?.contributorId === + contributor.userId ? ( + + ) : ( + + )} Remove from slot @@ -269,6 +338,60 @@ export function Model4MaintainerDashboard({ + + {/* Modals */} + + + + + Submissions for {selectedContributor?.userName} + + + Review the submitted work from this contributor. + + +
+ No submissions found for this contributor. +
+ + + +
+
+ + + + + Message {selectedContributor?.userName} + + Send a message directly to this contributor regarding their + application. + + +
+