From b6a71052021e83c229c5bea231afea4fa7dde6be Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Wed, 12 Nov 2025 03:33:28 +0000 Subject: [PATCH] Nexus Admin Dashboard - Opportunity/Dispute/Commission Management cgen-4f5956edbd544d119ba2f8b0eb95afb8 --- client/components/admin/AdminNexusManager.tsx | 622 ++++++++++++++++++ 1 file changed, 622 insertions(+) create mode 100644 client/components/admin/AdminNexusManager.tsx diff --git a/client/components/admin/AdminNexusManager.tsx b/client/components/admin/AdminNexusManager.tsx new file mode 100644 index 00000000..9b84fc83 --- /dev/null +++ b/client/components/admin/AdminNexusManager.tsx @@ -0,0 +1,622 @@ +import React, { useState, useEffect } from "react"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Briefcase, + AlertTriangle, + TrendingUp, + XCircle, + CheckCircle, + Search, + Eye, + Flag, +} from "lucide-react"; +import { aethexToast } from "@/lib/aethex-toast"; + +interface Opportunity { + id: string; + title: string; + category: string; + budget_min?: number; + budget_max?: number; + status: "open" | "in_progress" | "filled" | "closed" | "cancelled"; + application_count: number; + posted_by_email?: string; + created_at: string; + is_featured: boolean; +} + +interface Dispute { + id: string; + contract_id: string; + reported_by_email?: string; + reason: string; + status: "open" | "reviewing" | "resolved" | "escalated"; + created_at: string; + resolution_notes?: string; +} + +interface Commission { + period_start: string; + period_end: string; + total_volume: number; + total_commission: number; + creator_payouts: number; + aethex_revenue: number; + status: "pending" | "settled" | "disputed"; +} + +export default function AdminNexusManager() { + const [opportunities, setOpportunities] = useState([]); + const [disputes, setDisputes] = useState([]); + const [commissions, setCommissions] = useState([]); + const [loadingOpp, setLoadingOpp] = useState(true); + const [loadingDisputes, setLoadingDisputes] = useState(true); + const [loadingCommissions, setLoadingCommissions] = useState(true); + const [searchOpp, setSearchOpp] = useState(""); + const [disputeFilter, setDisputeFilter] = useState<"all" | "open" | "resolved">("all"); + const [selectedDispute, setSelectedDispute] = useState(null); + const [disputeDialogOpen, setDisputeDialogOpen] = useState(false); + const [disputeResolution, setDisputeResolution] = useState(""); + const [disputeAction, setDisputeAction] = useState<"resolve" | "escalate">( + "resolve" + ); + + useEffect(() => { + fetchOpportunities(); + fetchDisputes(); + fetchCommissions(); + }, []); + + const fetchOpportunities = async () => { + try { + setLoadingOpp(true); + const response = await fetch("/api/admin/nexus/opportunities"); + if (!response.ok) throw new Error("Failed to fetch opportunities"); + const data = await response.json(); + setOpportunities(data || []); + } catch (error) { + aethexToast.error("Failed to load opportunities"); + console.error(error); + } finally { + setLoadingOpp(false); + } + }; + + const fetchDisputes = async () => { + try { + setLoadingDisputes(true); + const response = await fetch("/api/admin/nexus/disputes"); + if (!response.ok) throw new Error("Failed to fetch disputes"); + const data = await response.json(); + setDisputes(data || []); + } catch (error) { + aethexToast.error("Failed to load disputes"); + console.error(error); + } finally { + setLoadingDisputes(false); + } + }; + + const fetchCommissions = async () => { + try { + setLoadingCommissions(true); + const response = await fetch("/api/admin/nexus/commissions"); + if (!response.ok) throw new Error("Failed to fetch commissions"); + const data = await response.json(); + setCommissions(data || []); + } catch (error) { + aethexToast.error("Failed to load commissions"); + console.error(error); + } finally { + setLoadingCommissions(false); + } + }; + + const handleModerateOpportunity = async ( + opportunityId: string, + status: "open" | "filled" | "closed" | "cancelled" + ) => { + try { + const response = await fetch( + `/api/admin/nexus/opportunities/${opportunityId}`, + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ status }), + } + ); + + if (!response.ok) throw new Error("Failed to update opportunity"); + aethexToast.success(`Opportunity marked as ${status}`); + fetchOpportunities(); + } catch (error) { + aethexToast.error("Failed to update opportunity"); + console.error(error); + } + }; + + const handleFeatureOpportunity = async ( + opportunityId: string, + featured: boolean + ) => { + try { + const response = await fetch( + `/api/admin/nexus/opportunities/${opportunityId}`, + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ is_featured: featured }), + } + ); + + if (!response.ok) throw new Error("Failed to update opportunity"); + aethexToast.success( + `Opportunity ${featured ? "featured" : "unfeatured"}` + ); + fetchOpportunities(); + } catch (error) { + aethexToast.error("Failed to update opportunity"); + console.error(error); + } + }; + + const handleDisputeResolution = async () => { + if (!selectedDispute) return; + + try { + const response = await fetch( + `/api/admin/nexus/disputes/${selectedDispute.id}`, + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + status: disputeAction === "resolve" ? "resolved" : "escalated", + resolution_notes: disputeResolution, + }), + } + ); + + if (!response.ok) throw new Error("Failed to update dispute"); + aethexToast.success( + `Dispute ${disputeAction === "resolve" ? "resolved" : "escalated"}` + ); + setDisputeDialogOpen(false); + setSelectedDispute(null); + setDisputeResolution(""); + fetchDisputes(); + } catch (error) { + aethexToast.error("Failed to update dispute"); + console.error(error); + } + }; + + const filteredOpportunities = opportunities.filter((o) => + o.title.toLowerCase().includes(searchOpp.toLowerCase()) + ); + + const filteredDisputes = disputes.filter((d) => { + if (disputeFilter === "all") return true; + if (disputeFilter === "open") return d.status === "open"; + if (disputeFilter === "resolved") return d.status === "resolved"; + return true; + }); + + const openOpportunities = opportunities.filter( + (o) => o.status === "open" + ).length; + const openDisputes = disputes.filter((d) => d.status === "open").length; + const totalCommissionsRevenue = commissions.reduce( + (sum, c) => sum + c.aethex_revenue, + 0 + ); + + return ( +
+ {/* Overview Cards */} +
+ + + + Total Opportunities + + + +
{opportunities.length}
+

+ {openOpportunities} open +

+
+
+ + + + + Open Disputes + + + +
{openDisputes}
+

+ Requires attention +

+
+
+ + + + + Commission Revenue + + + +
+ ${totalCommissionsRevenue.toFixed(0)} +
+

+ 20% of marketplace +

+
+
+ + + + + Ledger Periods + + + +
{commissions.length}
+

+ Commission records +

+
+
+
+ + {/* Tabs */} + + + + + Opportunities + + + + Disputes + + + + Commissions + + + + {/* OPPORTUNITIES TAB */} + +
+
+ + setSearchOpp(e.target.value)} + className="pl-10" + /> +
+
+ + {loadingOpp ? ( + + +

Loading opportunities...

+
+
+ ) : filteredOpportunities.length === 0 ? ( + + +

No opportunities found

+
+
+ ) : ( +
+ {filteredOpportunities.map((opp) => ( + + +
+
+
+

{opp.title}

+ {opp.is_featured && ( + Featured + )} + + {opp.status} + +
+

+ Category: {opp.category} • {opp.application_count}{" "} + applications +

+ {opp.budget_min && opp.budget_max && ( +

+ ${opp.budget_min} - ${opp.budget_max} +

+ )} +
+
+ {opp.status === "open" && ( + <> + + + + )} + {opp.status !== "cancelled" && ( + + )} +
+
+
+
+ ))} +
+ )} +
+ + {/* DISPUTES TAB */} + +
+ +
+ + {loadingDisputes ? ( + + +

Loading disputes...

+
+
+ ) : filteredDisputes.length === 0 ? ( + + +

No disputes found

+
+
+ ) : ( +
+ {filteredDisputes.map((dispute) => ( + + +
+
+
+ +

{dispute.reason}

+ + {dispute.status} + +
+

+ Reported by: {dispute.reported_by_email} +

+ {dispute.resolution_notes && ( +

+ Resolution: {dispute.resolution_notes} +

+ )} +
+ {dispute.status === "open" && ( + + )} +
+
+
+ ))} +
+ )} +
+ + {/* COMMISSIONS TAB */} + + {loadingCommissions ? ( + + +

Loading commissions...

+
+
+ ) : commissions.length === 0 ? ( + + +

No commission records

+
+
+ ) : ( +
+ {commissions.map((commission, idx) => ( + + +
+
+

Period

+

+ {new Date( + commission.period_start + ).toLocaleDateString()}{" "} + -{" "} + {new Date(commission.period_end).toLocaleDateString()} +

+
+
+

+ Total Volume +

+

+ ${commission.total_volume.toFixed(2)} +

+
+
+

+ AeThex Revenue (20%) +

+

+ ${commission.aethex_revenue.toFixed(2)} +

+
+
+

Status

+ + {commission.status} + +
+
+
+
+ ))} +
+ )} +
+
+ + {/* Dispute Resolution Dialog */} + + + Resolve Dispute +
+
+ +