From 4c54cc4a65f9acbf5bfc9081ea741fb657c8e0b5 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Tue, 11 Nov 2025 23:24:51 +0000 Subject: [PATCH] Admin dashboard for Ethos artist verification cgen-d5b5aa6e7bd440e5aa4aeed9190f8b82 --- client/pages/admin/AdminEthosVerification.tsx | 388 ++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 client/pages/admin/AdminEthosVerification.tsx diff --git a/client/pages/admin/AdminEthosVerification.tsx b/client/pages/admin/AdminEthosVerification.tsx new file mode 100644 index 00000000..3d37398e --- /dev/null +++ b/client/pages/admin/AdminEthosVerification.tsx @@ -0,0 +1,388 @@ +import { useEffect, useState } from "react"; +import { useAuth } from "@/contexts/AuthContext"; +import { useAethexToast } from "@/hooks/use-aethex-toast"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Input } from "@/components/ui/input"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { CheckCircle, XCircle, Clock, ChevronRight, Music } from "lucide-react"; + +interface VerificationRequest { + id: string; + user_id: string; + artist_profile_id: string; + status: "pending" | "approved" | "rejected"; + submitted_at: string; + reviewed_at?: string; + rejection_reason?: string; + submission_notes?: string; + portfolio_links?: string[]; + user_profiles?: { + full_name: string; + email: string; + avatar_url?: string; + }; + ethos_artist_profiles?: { + bio: string; + skills: string[]; + for_hire: boolean; + sample_price_track?: number; + }; +} + +export default function AdminEthosVerification() { + const { user } = useAuth(); + const toast = useAethexToast(); + + const [requests, setRequests] = useState([]); + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState("pending"); + const [selectedRequest, setSelectedRequest] = useState(null); + const [rejectionReason, setRejectionReason] = useState(""); + const [isConfirming, setIsConfirming] = useState(false); + const [confirmAction, setConfirmAction] = useState<"approve" | "reject" | null>(null); + + useEffect(() => { + fetchRequests(); + }, [activeTab]); + + const fetchRequests = async () => { + try { + setLoading(true); + const response = await fetch(`/api/ethos/verification?status=${activeTab}`, { + headers: { + "x-user-id": user?.id || "", + }, + }); + + if (!response.ok) throw new Error("Failed to fetch requests"); + + const { data } = await response.json(); + setRequests(data); + } catch (error) { + console.error(error); + toast.error("Failed to load verification requests"); + } finally { + setLoading(false); + } + }; + + const handleApprove = (request: VerificationRequest) => { + setSelectedRequest(request); + setConfirmAction("approve"); + setIsConfirming(true); + }; + + const handleReject = (request: VerificationRequest) => { + setSelectedRequest(request); + setConfirmAction("reject"); + setRejectionReason(""); + setIsConfirming(true); + }; + + const confirmDecision = async () => { + if (!selectedRequest) return; + + try { + const response = await fetch("/api/ethos/verification", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-user-id": user?.id || "", + }, + body: JSON.stringify({ + action: confirmAction, + request_id: selectedRequest.id, + rejection_reason: rejectionReason, + }), + }); + + if (!response.ok) throw new Error(`Failed to ${confirmAction} artist`); + + toast.success( + confirmAction === "approve" + ? "Artist verified successfully! Email sent." + : "Artist application rejected. Email sent.", + ); + + setIsConfirming(false); + setSelectedRequest(null); + setConfirmAction(null); + fetchRequests(); + } catch (error) { + console.error(error); + toast.error(`Failed to ${confirmAction} artist`); + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case "approved": + return ; + case "rejected": + return ; + default: + return ; + } + }; + + const stats = { + pending: requests.filter((r) => r.status === "pending").length, + approved: requests.filter((r) => r.status === "approved").length, + rejected: requests.filter((r) => r.status === "rejected").length, + }; + + return ( +
+
+

+ + Ethos Guild Artist Verification +

+

Manage artist verification applications and approve verified creators

+
+ + {/* Stats Cards */} +
+ + + Pending + + +
{stats.pending}
+

Applications awaiting review

+
+
+ + + + Approved + + +
{stats.approved}
+

Verified artists

+
+
+ + + + Rejected + + +
{stats.rejected}
+

Declined applications

+
+
+
+ + {/* Requests Tabs */} + + + Verification Requests + Review and approve artist applications + + + + + Pending ({stats.pending}) + Approved ({stats.approved}) + Rejected ({stats.rejected}) + + + + {loading ? ( +
Loading requests...
+ ) : requests.length === 0 ? ( +
No {activeTab} verification requests
+ ) : ( + requests.map((request) => ( + + )) + )} +
+
+
+
+ + {/* Confirmation Dialog */} + + + + {confirmAction === "approve" ? "Verify Artist?" : "Reject Artist?"} + + + {confirmAction === "approve" + ? `Verify ${selectedRequest?.user_profiles?.full_name} as an Ethos Guild artist? They will be notified by email and can start uploading tracks.` + : `Reject ${selectedRequest?.user_profiles?.full_name}'s application? They will be notified by email.`} + + + {confirmAction === "reject" && ( +
+ +