diff --git a/client/pages/ethos/LicensingDashboard.tsx b/client/pages/ethos/LicensingDashboard.tsx new file mode 100644 index 00000000..bf71d52f --- /dev/null +++ b/client/pages/ethos/LicensingDashboard.tsx @@ -0,0 +1,389 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import Layout from "@/components/Layout"; +import SEO from "@/components/SEO"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs"; +import { useAuth } from "@/contexts/AuthContext"; +import { useAethexToast } from "@/hooks/use-aethex-toast"; +import { CheckCircle2, Clock, FileText, AlertCircle } from "lucide-react"; + +interface LicensingAgreement { + id: string; + track_id: string; + licensee_id: string; + license_type: string; + agreement_url?: string; + approved: boolean; + created_at: string; + expires_at?: string; + ethos_tracks?: { + title: string; + user_id: string; + }; + user_profiles?: { + full_name: string; + avatar_url?: string; + }; +} + +export default function LicensingDashboard() { + const { user } = useAuth(); + const navigate = useNavigate(); + const toast = useAethexToast(); + + const [agreements, setAgreements] = useState([]); + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState("pending"); + + useEffect(() => { + if (!user) { + navigate("/login"); + return; + } + + const fetchAgreements = async () => { + try { + const status = activeTab === "all" ? "all" : activeTab; + const res = await fetch( + `/api/ethos/licensing-agreements?status=${status}`, + { + headers: { "x-user-id": user.id }, + }, + ); + + if (res.ok) { + const { data } = await res.json(); + setAgreements(data || []); + } + } catch (error) { + console.error("Failed to fetch agreements:", error); + } finally { + setLoading(false); + } + }; + + fetchAgreements(); + }, [user, navigate, activeTab]); + + const handleApprove = async (id: string) => { + try { + const res = await fetch(`/api/ethos/licensing-agreements?id=${id}`, { + method: "PUT", + headers: { + "x-user-id": user!.id, + "Content-Type": "application/json", + }, + body: JSON.stringify({ approved: true }), + }); + + if (res.ok) { + setAgreements((prev) => + prev.map((a) => + a.id === id ? { ...a, approved: true } : a, + ), + ); + toast.success({ + title: "Agreement approved", + description: "The licensing agreement has been approved", + }); + } + } catch (error) { + toast.error({ + title: "Error", + description: String(error), + }); + } + }; + + const handleDelete = async (id: string) => { + if (!confirm("Delete this agreement?")) return; + + try { + await fetch(`/api/ethos/licensing-agreements?id=${id}`, { + method: "DELETE", + headers: { "x-user-id": user!.id }, + }); + + setAgreements((prev) => prev.filter((a) => a.id !== id)); + toast.success({ + title: "Agreement deleted", + }); + } catch (error) { + toast.error({ + title: "Error", + description: String(error), + }); + } + }; + + if (loading) { + return
Loading agreements...
; + } + + const pendingCount = agreements.filter((a) => !a.approved).length; + const approvedCount = agreements.filter((a) => a.approved).length; + + return ( + <> + + +
+
+ {/* Header */} +
+

+ + Licensing Agreements +

+

+ Manage commercial licensing requests for your tracks +

+
+ + {/* Stats */} +
+ + +

+ Total Agreements +

+

+ {agreements.length} +

+
+
+ + + +

Pending

+

+ {pendingCount} +

+
+
+ + + +

Approved

+

+ {approvedCount} +

+
+
+ + + +

+ Potential Revenue +

+

Coming

+
+
+
+ + {/* Tabs */} + + + + Pending ({pendingCount}) + + + Approved ({approvedCount}) + + All Agreements + + + + {agreements.filter((a) => !a.approved).length === 0 ? ( + + + No pending licensing requests + + + ) : ( + agreements + .filter((a) => !a.approved) + .map((agreement) => ( + handleApprove(agreement.id)} + onDelete={() => handleDelete(agreement.id)} + /> + )) + )} + + + + {agreements.filter((a) => a.approved).length === 0 ? ( + + + No approved agreements yet + + + ) : ( + agreements + .filter((a) => a.approved) + .map((agreement) => ( + handleDelete(agreement.id)} + /> + )) + )} + + + + {agreements.length === 0 ? ( + + + No licensing agreements yet + + + ) : ( + agreements.map((agreement) => ( + handleApprove(agreement.id) + : undefined + } + onDelete={() => handleDelete(agreement.id)} + /> + )) + )} + + +
+
+
+ + ); +} + +interface AgreementCardProps { + agreement: LicensingAgreement; + onApprove?: () => void; + onDelete: () => void; +} + +function AgreementCard({ + agreement, + onApprove, + onDelete, +}: AgreementCardProps) { + const formatDate = (date: string) => { + return new Date(date).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }); + }; + + const licenseTypeLabel = { + commercial_one_time: "One-time License", + commercial_exclusive: "Exclusive License", + broadcast: "Broadcast License", + }[agreement.license_type] || agreement.license_type; + + return ( + + +
+
+
+

+ {agreement.ethos_tracks?.title || "Unknown Track"} +

+ {agreement.approved ? ( + + + Approved + + ) : ( + + + Pending + + )} +
+ +

+ Licensed by:{" "} + + {agreement.user_profiles?.full_name || "Unknown"} + +

+ +
+
+

License Type

+

{licenseTypeLabel}

+
+
+

Requested

+

{formatDate(agreement.created_at)}

+
+ {agreement.expires_at && ( +
+

Expires

+

{formatDate(agreement.expires_at)}

+
+ )} +
+
+ + {agreement.agreement_url && ( + + )} +
+ +
+ {onApprove && ( + + )} + +
+
+
+ ); +}