Prettier format pending files

This commit is contained in:
Builder.io 2025-11-13 03:24:02 +00:00
parent 355d2319dc
commit 381a7dff27
15 changed files with 266 additions and 135 deletions

View file

@ -256,7 +256,10 @@ const App = () => (
<Route path="/web3-callback" element={<Web3Callback />} /> <Route path="/web3-callback" element={<Web3Callback />} />
<Route path="/discord-verify" element={<DiscordVerify />} /> <Route path="/discord-verify" element={<DiscordVerify />} />
<Route path="/discord" element={<DiscordActivity />} /> <Route path="/discord" element={<DiscordActivity />} />
<Route path="/discord/callback" element={<DiscordOAuthCallback />} /> <Route
path="/discord/callback"
element={<DiscordOAuthCallback />}
/>
{/* Creator Network routes */} {/* Creator Network routes */}
<Route path="/creators" element={<CreatorDirectory />} /> <Route path="/creators" element={<CreatorDirectory />} />

View file

@ -93,7 +93,9 @@ export default function AdminBlogManager() {
const handleDeleteBlogPost = useCallback(async (slug: string) => { const handleDeleteBlogPost = useCallback(async (slug: string) => {
setDeleting(slug); setDeleting(slug);
try { try {
const res = await fetch(`${API_BASE}/api/blog/${slug}`, { method: "DELETE" }); const res = await fetch(`${API_BASE}/api/blog/${slug}`, {
method: "DELETE",
});
if (res.ok) { if (res.ok) {
setBlogPosts((posts) => posts.filter((p) => p.slug !== slug)); setBlogPosts((posts) => posts.filter((p) => p.slug !== slug));
aethexToast.success({ aethexToast.success({

View file

@ -157,9 +157,12 @@ export function AdminDiscordManagement() {
const handleDeleteMapping = async (id: string) => { const handleDeleteMapping = async (id: string) => {
try { try {
const response = await fetch(`${API_BASE}/api/discord/role-mappings?id=${id}`, { const response = await fetch(
method: "DELETE", `${API_BASE}/api/discord/role-mappings?id=${id}`,
}); {
method: "DELETE",
},
);
if (!response.ok) throw new Error("Failed to delete mapping"); if (!response.ok) throw new Error("Failed to delete mapping");
setMappings(mappings.filter((m) => m.id !== id)); setMappings(mappings.filter((m) => m.id !== id));
@ -193,13 +196,16 @@ export function AdminDiscordManagement() {
console.log("[Discord] Registering commands with token..."); console.log("[Discord] Registering commands with token...");
const response = await fetch(`${API_BASE}/api/discord/admin-register-commands`, { const response = await fetch(
method: "POST", `${API_BASE}/api/discord/admin-register-commands`,
headers: { {
"Content-Type": "application/json", method: "POST",
Authorization: `Bearer ${adminToken}`, headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${adminToken}`,
},
}, },
}); );
console.log("[Discord] Response status:", response.status); console.log("[Discord] Response status:", response.status);

View file

@ -126,7 +126,9 @@ export default function AdminFoundationManager() {
const fetchAchievements = async () => { const fetchAchievements = async () => {
try { try {
setLoadingAchievements(true); setLoadingAchievements(true);
const response = await fetch(`${API_BASE}/api/admin/foundation/achievements`); const response = await fetch(
`${API_BASE}/api/admin/foundation/achievements`,
);
if (!response.ok) throw new Error("Failed to fetch achievements"); if (!response.ok) throw new Error("Failed to fetch achievements");
const data = await response.json(); const data = await response.json();
setAchievements(data || []); setAchievements(data || []);

View file

@ -216,9 +216,12 @@ export default function AdminStaffDirectory() {
try { try {
setIsDeleting(true); setIsDeleting(true);
const response = await fetch(`${API_BASE}/api/staff/members-detail?id=${memberId}`, { const response = await fetch(
method: "DELETE", `${API_BASE}/api/staff/members-detail?id=${memberId}`,
}); {
method: "DELETE",
},
);
const result = await response.json(); const result = await response.json();

View file

@ -506,11 +506,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
) { ) {
try { try {
// Check if this email is linked to another account // Check if this email is linked to another account
const response = await fetch(`${API_BASE}/api/user/resolve-linked-email`, { const response = await fetch(
method: "POST", `${API_BASE}/api/user/resolve-linked-email`,
headers: { "Content-Type": "application/json" }, {
body: JSON.stringify({ email }), method: "POST",
}); headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
},
);
if (response.ok) { if (response.ok) {
const { primaryEmail } = await response.json(); const { primaryEmail } = await response.json();

View file

@ -259,7 +259,9 @@ export const aethexSocialService = {
async listMentorshipRequests(userId: string, role?: "mentor" | "mentee") { async listMentorshipRequests(userId: string, role?: "mentor" | "mentee") {
const qs = new URLSearchParams({ user_id: userId }); const qs = new URLSearchParams({ user_id: userId });
if (role) qs.set("role", role); if (role) qs.set("role", role);
const resp = await fetch(`${API_BASE}/api/mentorship/requests?${qs.toString()}`); const resp = await fetch(
`${API_BASE}/api/mentorship/requests?${qs.toString()}`,
);
if (!resp.ok) return [] as any[]; if (!resp.ok) return [] as any[];
return (await resp.json()) as any[]; return (await resp.json()) as any[];
}, },

View file

@ -28,44 +28,46 @@ export default function BlogPost() {
try { try {
if (!slug) return; if (!slug) return;
// Primary: try server API // Primary: try server API
let res = await fetch(`${API_BASE}/api/blog/${encodeURIComponent(slug)}`); let res = await fetch(
`${API_BASE}/api/blog/${encodeURIComponent(slug)}`,
);
let data: any = null; let data: any = null;
try { try {
// Attempt to parse JSON response from server route // Attempt to parse JSON response from server route
if (res.ok) data = await res.json(); if (res.ok) data = await res.json();
} catch (e) { } catch (e) {
// If server returned HTML (dev server) or invalid JSON, fall back to Supabase REST // If server returned HTML (dev server) or invalid JSON, fall back to Supabase REST
try { try {
const sbUrl = import.meta.env.VITE_SUPABASE_URL; const sbUrl = import.meta.env.VITE_SUPABASE_URL;
const sbKey = import.meta.env.VITE_SUPABASE_ANON_KEY; const sbKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
if (sbUrl && sbKey) { if (sbUrl && sbKey) {
const url = `${sbUrl.replace(/\/$/, "")}/rest/v1/blog_posts?slug=eq.${encodeURIComponent( const url = `${sbUrl.replace(/\/$/, "")}/rest/v1/blog_posts?slug=eq.${encodeURIComponent(
String(slug), String(slug),
)}&select=id,slug,title,excerpt,author,date,read_time,category,image,body_html,published_at`; )}&select=id,slug,title,excerpt,author,date,read_time,category,image,body_html,published_at`;
const sbRes = await fetch(url, { const sbRes = await fetch(url, {
headers: { headers: {
apikey: sbKey as string, apikey: sbKey as string,
Authorization: `Bearer ${sbKey}`, Authorization: `Bearer ${sbKey}`,
}, },
}); });
if (sbRes.ok) { if (sbRes.ok) {
const arr = await sbRes.json(); const arr = await sbRes.json();
data = Array.isArray(arr) && arr.length ? arr[0] : null; data = Array.isArray(arr) && arr.length ? arr[0] : null;
}
} }
} catch (err) {
console.warn("Supabase fallback fetch failed:", err);
} }
} catch (err) {
console.warn("Supabase fallback fetch failed:", err);
} }
}
// If API and Supabase both fail, try seed data // If API and Supabase both fail, try seed data
if (!data) { if (!data) {
const seedPost = blogSeedPosts.find((p) => p.slug === slug); const seedPost = blogSeedPosts.find((p) => p.slug === slug);
if (seedPost) { if (seedPost) {
data = seedPost; data = seedPost;
}
} }
}
if (!cancelled) setPost(data); if (!cancelled) setPost(data);
} catch (e) { } catch (e) {

View file

@ -1132,9 +1132,12 @@ export default function Dashboard() {
onChange={async (e) => { onChange={async (e) => {
const ensureBuckets = async () => { const ensureBuckets = async () => {
try { try {
await fetch(`${API_BASE}/api/storage/ensure-buckets`, { await fetch(
method: "POST", `${API_BASE}/api/storage/ensure-buckets`,
}); {
method: "POST",
},
);
} catch {} } catch {}
}; };
const file = e.target.files?.[0]; const file = e.target.files?.[0];
@ -1210,9 +1213,12 @@ export default function Dashboard() {
onChange={async (e) => { onChange={async (e) => {
const ensureBuckets = async () => { const ensureBuckets = async () => {
try { try {
await fetch(`${API_BASE}/api/storage/ensure-buckets`, { await fetch(
method: "POST", `${API_BASE}/api/storage/ensure-buckets`,
}); {
method: "POST",
},
);
} catch {} } catch {}
}; };
const file = e.target.files?.[0]; const file = e.target.files?.[0];

View file

@ -1,7 +1,13 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useAuth } from "@/contexts/AuthContext"; import { useAuth } from "@/contexts/AuthContext";
import { useAethexToast } from "@/hooks/use-aethex-toast"; import { useAethexToast } from "@/hooks/use-aethex-toast";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
@ -50,10 +56,13 @@ export default function AdminEthosVerification() {
const [requests, setRequests] = useState<VerificationRequest[]>([]); const [requests, setRequests] = useState<VerificationRequest[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState("pending"); const [activeTab, setActiveTab] = useState("pending");
const [selectedRequest, setSelectedRequest] = useState<VerificationRequest | null>(null); const [selectedRequest, setSelectedRequest] =
useState<VerificationRequest | null>(null);
const [rejectionReason, setRejectionReason] = useState(""); const [rejectionReason, setRejectionReason] = useState("");
const [isConfirming, setIsConfirming] = useState(false); const [isConfirming, setIsConfirming] = useState(false);
const [confirmAction, setConfirmAction] = useState<"approve" | "reject" | null>(null); const [confirmAction, setConfirmAction] = useState<
"approve" | "reject" | null
>(null);
useEffect(() => { useEffect(() => {
fetchRequests(); fetchRequests();
@ -62,11 +71,14 @@ export default function AdminEthosVerification() {
const fetchRequests = async () => { const fetchRequests = async () => {
try { try {
setLoading(true); setLoading(true);
const response = await fetch(`${API_BASE}/api/ethos/verification?status=${activeTab}`, { const response = await fetch(
headers: { `${API_BASE}/api/ethos/verification?status=${activeTab}`,
"x-user-id": user?.id || "", {
headers: {
"x-user-id": user?.id || "",
},
}, },
}); );
if (!response.ok) throw new Error("Failed to fetch requests"); if (!response.ok) throw new Error("Failed to fetch requests");
@ -152,7 +164,9 @@ export default function AdminEthosVerification() {
<Music className="w-8 h-8 text-pink-500" /> <Music className="w-8 h-8 text-pink-500" />
Ethos Guild Artist Verification Ethos Guild Artist Verification
</h1> </h1>
<p className="text-gray-400">Manage artist verification applications and approve verified creators</p> <p className="text-gray-400">
Manage artist verification applications and approve verified creators
</p>
</div> </div>
{/* Stats Cards */} {/* Stats Cards */}
@ -162,8 +176,12 @@ export default function AdminEthosVerification() {
<CardTitle className="text-lg">Pending</CardTitle> <CardTitle className="text-lg">Pending</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-3xl font-bold text-yellow-500">{stats.pending}</div> <div className="text-3xl font-bold text-yellow-500">
<p className="text-sm text-gray-400">Applications awaiting review</p> {stats.pending}
</div>
<p className="text-sm text-gray-400">
Applications awaiting review
</p>
</CardContent> </CardContent>
</Card> </Card>
@ -172,7 +190,9 @@ export default function AdminEthosVerification() {
<CardTitle className="text-lg">Approved</CardTitle> <CardTitle className="text-lg">Approved</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-3xl font-bold text-green-500">{stats.approved}</div> <div className="text-3xl font-bold text-green-500">
{stats.approved}
</div>
<p className="text-sm text-gray-400">Verified artists</p> <p className="text-sm text-gray-400">Verified artists</p>
</CardContent> </CardContent>
</Card> </Card>
@ -182,7 +202,9 @@ export default function AdminEthosVerification() {
<CardTitle className="text-lg">Rejected</CardTitle> <CardTitle className="text-lg">Rejected</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-3xl font-bold text-red-500">{stats.rejected}</div> <div className="text-3xl font-bold text-red-500">
{stats.rejected}
</div>
<p className="text-sm text-gray-400">Declined applications</p> <p className="text-sm text-gray-400">Declined applications</p>
</CardContent> </CardContent>
</Card> </Card>
@ -192,21 +214,33 @@ export default function AdminEthosVerification() {
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Verification Requests</CardTitle> <CardTitle>Verification Requests</CardTitle>
<CardDescription>Review and approve artist applications</CardDescription> <CardDescription>
Review and approve artist applications
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Tabs value={activeTab} onValueChange={setActiveTab}> <Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="mb-6"> <TabsList className="mb-6">
<TabsTrigger value="pending">Pending ({stats.pending})</TabsTrigger> <TabsTrigger value="pending">
<TabsTrigger value="approved">Approved ({stats.approved})</TabsTrigger> Pending ({stats.pending})
<TabsTrigger value="rejected">Rejected ({stats.rejected})</TabsTrigger> </TabsTrigger>
<TabsTrigger value="approved">
Approved ({stats.approved})
</TabsTrigger>
<TabsTrigger value="rejected">
Rejected ({stats.rejected})
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value={activeTab} className="space-y-4"> <TabsContent value={activeTab} className="space-y-4">
{loading ? ( {loading ? (
<div className="text-center py-8 text-gray-400">Loading requests...</div> <div className="text-center py-8 text-gray-400">
Loading requests...
</div>
) : requests.length === 0 ? ( ) : requests.length === 0 ? (
<div className="text-center py-8 text-gray-400">No {activeTab} verification requests</div> <div className="text-center py-8 text-gray-400">
No {activeTab} verification requests
</div>
) : ( ) : (
requests.map((request) => ( requests.map((request) => (
<VerificationRequestCard <VerificationRequestCard
@ -237,7 +271,9 @@ export default function AdminEthosVerification() {
{confirmAction === "reject" && ( {confirmAction === "reject" && (
<div className="mt-4"> <div className="mt-4">
<label className="block text-sm font-medium mb-2">Rejection Reason (optional)</label> <label className="block text-sm font-medium mb-2">
Rejection Reason (optional)
</label>
<Textarea <Textarea
placeholder="Provide feedback to help them improve their application..." placeholder="Provide feedback to help them improve their application..."
value={rejectionReason} value={rejectionReason}
@ -301,14 +337,23 @@ function VerificationRequestCard({
)} )}
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold">{request.user_profiles?.full_name}</h3> <h3 className="font-semibold">
<Badge variant="outline" className="text-xs flex items-center gap-1"> {request.user_profiles?.full_name}
</h3>
<Badge
variant="outline"
className="text-xs flex items-center gap-1"
>
{getStatusIcon(request.status)} {getStatusIcon(request.status)}
{request.status} {request.status}
</Badge> </Badge>
</div> </div>
<p className="text-sm text-gray-400">{request.user_profiles?.email}</p> <p className="text-sm text-gray-400">
<p className="text-xs text-gray-500 mt-1">Applied: {formattedDate}</p> {request.user_profiles?.email}
</p>
<p className="text-xs text-gray-500 mt-1">
Applied: {formattedDate}
</p>
</div> </div>
</div> </div>
@ -323,7 +368,11 @@ function VerificationRequestCard({
<XCircle className="w-4 h-4 mr-1" /> <XCircle className="w-4 h-4 mr-1" />
Reject Reject
</Button> </Button>
<Button size="sm" className="bg-green-600 hover:bg-green-700" onClick={() => onApprove(request)}> <Button
size="sm"
className="bg-green-600 hover:bg-green-700"
onClick={() => onApprove(request)}
>
<CheckCircle className="w-4 h-4 mr-1" /> <CheckCircle className="w-4 h-4 mr-1" />
Verify Verify
</Button> </Button>
@ -335,7 +384,9 @@ function VerificationRequestCard({
{request.ethos_artist_profiles?.bio && ( {request.ethos_artist_profiles?.bio && (
<div> <div>
<p className="text-xs font-medium text-gray-400 mb-1">Bio</p> <p className="text-xs font-medium text-gray-400 mb-1">Bio</p>
<p className="text-sm text-gray-300">{request.ethos_artist_profiles.bio}</p> <p className="text-sm text-gray-300">
{request.ethos_artist_profiles.bio}
</p>
</div> </div>
)} )}
@ -354,14 +405,18 @@ function VerificationRequestCard({
{request.submission_notes && ( {request.submission_notes && (
<div> <div>
<p className="text-xs font-medium text-gray-400 mb-1">Application Notes</p> <p className="text-xs font-medium text-gray-400 mb-1">
Application Notes
</p>
<p className="text-sm text-gray-300">{request.submission_notes}</p> <p className="text-sm text-gray-300">{request.submission_notes}</p>
</div> </div>
)} )}
{request.portfolio_links && request.portfolio_links.length > 0 && ( {request.portfolio_links && request.portfolio_links.length > 0 && (
<div> <div>
<p className="text-xs font-medium text-gray-400 mb-2">Portfolio Links</p> <p className="text-xs font-medium text-gray-400 mb-2">
Portfolio Links
</p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{request.portfolio_links.map((link, idx) => ( {request.portfolio_links.map((link, idx) => (
<a <a
@ -381,7 +436,9 @@ function VerificationRequestCard({
{request.rejection_reason && request.status === "rejected" && ( {request.rejection_reason && request.status === "rejected" && (
<div className="mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded"> <div className="mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded">
<p className="text-xs font-medium text-red-400 mb-1">Rejection Reason</p> <p className="text-xs font-medium text-red-400 mb-1">
Rejection Reason
</p>
<p className="text-sm text-red-300">{request.rejection_reason}</p> <p className="text-sm text-red-300">{request.rejection_reason}</p>
</div> </div>
)} )}

View file

@ -68,11 +68,19 @@ export default function ArtistProfile() {
}, [userId]); }, [userId]);
if (loading) { if (loading) {
return <Layout><div className="py-20 text-center">Loading artist profile...</div></Layout>; return (
<Layout>
<div className="py-20 text-center">Loading artist profile...</div>
</Layout>
);
} }
if (!artist) { if (!artist) {
return <Layout><div className="py-20 text-center">Artist not found</div></Layout>; return (
<Layout>
<div className="py-20 text-center">Artist not found</div>
</Layout>
);
} }
const memberSince = new Date(artist.created_at).toLocaleDateString("en-US", { const memberSince = new Date(artist.created_at).toLocaleDateString("en-US", {
@ -130,7 +138,9 @@ export default function ArtistProfile() {
</div> </div>
<div> <div>
<p className="text-slate-500">Member Since</p> <p className="text-slate-500">Member Since</p>
<p className="text-xl font-bold text-white">{memberSince}</p> <p className="text-xl font-bold text-white">
{memberSince}
</p>
</div> </div>
</div> </div>
@ -235,7 +245,9 @@ export default function ArtistProfile() {
> >
<CardContent className="p-4 flex items-center justify-between"> <CardContent className="p-4 flex items-center justify-between">
<div className="flex-1"> <div className="flex-1">
<h3 className="text-white font-semibold">{track.title}</h3> <h3 className="text-white font-semibold">
{track.title}
</h3>
<div className="flex gap-2 mt-1"> <div className="flex gap-2 mt-1">
{track.genre.map((g) => ( {track.genre.map((g) => (
<Badge <Badge

View file

@ -118,9 +118,12 @@ export default function ArtistSettings() {
} }
// Fetch verification status // Fetch verification status
const verRes = await fetch(`${API_BASE}/api/ethos/verification?status=pending`, { const verRes = await fetch(
headers: { "x-user-id": user.id }, `${API_BASE}/api/ethos/verification?status=pending`,
}); {
headers: { "x-user-id": user.id },
},
);
if (verRes.ok) { if (verRes.ok) {
const { data: requests } = await verRes.json(); const { data: requests } = await verRes.json();

View file

@ -11,12 +11,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
import { useAuth } from "@/contexts/AuthContext"; import { useAuth } from "@/contexts/AuthContext";
import { useAethexToast } from "@/hooks/use-aethex-toast"; import { useAethexToast } from "@/hooks/use-aethex-toast";
import { CheckCircle2, Clock, FileText, AlertCircle } from "lucide-react"; import { CheckCircle2, Clock, FileText, AlertCircle } from "lucide-react";
@ -84,20 +79,21 @@ export default function LicensingDashboard() {
const handleApprove = async (id: string) => { const handleApprove = async (id: string) => {
try { try {
const res = await fetch(`${API_BASE}/api/ethos/licensing-agreements?id=${id}`, { const res = await fetch(
method: "PUT", `${API_BASE}/api/ethos/licensing-agreements?id=${id}`,
headers: { {
"x-user-id": user!.id, method: "PUT",
"Content-Type": "application/json", headers: {
"x-user-id": user!.id,
"Content-Type": "application/json",
},
body: JSON.stringify({ approved: true }),
}, },
body: JSON.stringify({ approved: true }), );
});
if (res.ok) { if (res.ok) {
setAgreements((prev) => setAgreements((prev) =>
prev.map((a) => prev.map((a) => (a.id === id ? { ...a, approved: true } : a)),
a.id === id ? { ...a, approved: true } : a,
),
); );
toast.success({ toast.success({
title: "Agreement approved", title: "Agreement approved",
@ -134,7 +130,11 @@ export default function LicensingDashboard() {
}; };
if (loading) { if (loading) {
return <Layout><div className="py-20 text-center">Loading agreements...</div></Layout>; return (
<Layout>
<div className="py-20 text-center">Loading agreements...</div>
</Layout>
);
} }
const pendingCount = agreements.filter((a) => !a.approved).length; const pendingCount = agreements.filter((a) => !a.approved).length;
@ -172,7 +172,9 @@ export default function LicensingDashboard() {
<Card className="bg-slate-900/50 border-slate-800"> <Card className="bg-slate-900/50 border-slate-800">
<CardContent className="pt-6"> <CardContent className="pt-6">
<p className="text-slate-400 text-xs uppercase mb-2">Pending</p> <p className="text-slate-400 text-xs uppercase mb-2">
Pending
</p>
<p className="text-2xl font-bold text-yellow-400"> <p className="text-2xl font-bold text-yellow-400">
{pendingCount} {pendingCount}
</p> </p>
@ -181,7 +183,9 @@ export default function LicensingDashboard() {
<Card className="bg-slate-900/50 border-slate-800"> <Card className="bg-slate-900/50 border-slate-800">
<CardContent className="pt-6"> <CardContent className="pt-6">
<p className="text-slate-400 text-xs uppercase mb-2">Approved</p> <p className="text-slate-400 text-xs uppercase mb-2">
Approved
</p>
<p className="text-2xl font-bold text-green-400"> <p className="text-2xl font-bold text-green-400">
{approvedCount} {approvedCount}
</p> </p>
@ -287,11 +291,7 @@ interface AgreementCardProps {
onDelete: () => void; onDelete: () => void;
} }
function AgreementCard({ function AgreementCard({ agreement, onApprove, onDelete }: AgreementCardProps) {
agreement,
onApprove,
onDelete,
}: AgreementCardProps) {
const formatDate = (date: string) => { const formatDate = (date: string) => {
return new Date(date).toLocaleDateString("en-US", { return new Date(date).toLocaleDateString("en-US", {
year: "numeric", year: "numeric",
@ -300,11 +300,12 @@ function AgreementCard({
}); });
}; };
const licenseTypeLabel = { const licenseTypeLabel =
commercial_one_time: "One-time License", {
commercial_exclusive: "Exclusive License", commercial_one_time: "One-time License",
broadcast: "Broadcast License", commercial_exclusive: "Exclusive License",
}[agreement.license_type] || agreement.license_type; broadcast: "Broadcast License",
}[agreement.license_type] || agreement.license_type;
return ( return (
<Card className="bg-slate-900/50 border-slate-800"> <Card className="bg-slate-900/50 border-slate-800">
@ -347,7 +348,9 @@ function AgreementCard({
{agreement.expires_at && ( {agreement.expires_at && (
<div> <div>
<p className="text-slate-500">Expires</p> <p className="text-slate-500">Expires</p>
<p className="text-white">{formatDate(agreement.expires_at)}</p> <p className="text-white">
{formatDate(agreement.expires_at)}
</p>
</div> </div>
)} )}
</div> </div>
@ -360,7 +363,11 @@ function AgreementCard({
asChild asChild
className="border-slate-700" className="border-slate-700"
> >
<a href={agreement.agreement_url} target="_blank" rel="noreferrer"> <a
href={agreement.agreement_url}
target="_blank"
rel="noreferrer"
>
View Contract View Contract
</a> </a>
</Button> </Button>

View file

@ -69,15 +69,21 @@ export default function TrackLibrary() {
params.append("limit", "100"); params.append("limit", "100");
if (searchQuery) params.append("search", searchQuery); if (searchQuery) params.append("search", searchQuery);
if (selectedGenre !== "All Genres") params.append("genre", selectedGenre); if (selectedGenre !== "All Genres")
if (licenseFilter !== "all") params.append("licenseType", licenseFilter); params.append("genre", selectedGenre);
if (licenseFilter !== "all")
params.append("licenseType", licenseFilter);
const res = await fetch(`${API_BASE}/api/ethos/tracks?${params}`); const res = await fetch(`${API_BASE}/api/ethos/tracks?${params}`);
const { data } = await res.json(); const { data } = await res.json();
let sorted = [...data]; let sorted = [...data];
if (sortBy === "newest") { if (sortBy === "newest") {
sorted.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); sorted.sort(
(a, b) =>
new Date(b.created_at).getTime() -
new Date(a.created_at).getTime(),
);
} else if (sortBy === "popular") { } else if (sortBy === "popular") {
sorted.sort((a, b) => b.download_count - a.download_count); sorted.sort((a, b) => b.download_count - a.download_count);
} }
@ -122,8 +128,9 @@ export default function TrackLibrary() {
Discover Ethos Music & SFX Discover Ethos Music & SFX
</h1> </h1>
<p className="text-lg text-slate-400 max-w-2xl"> <p className="text-lg text-slate-400 max-w-2xl">
Browse original music and sound effects created by Ethos Guild artists. Browse original music and sound effects created by Ethos
Use freely in your projects or license commercially. Guild artists. Use freely in your projects or license
commercially.
</p> </p>
</div> </div>
@ -144,7 +151,10 @@ export default function TrackLibrary() {
<label className="text-xs uppercase text-slate-500 mb-2 block"> <label className="text-xs uppercase text-slate-500 mb-2 block">
Genre Genre
</label> </label>
<Select value={selectedGenre} onValueChange={setSelectedGenre}> <Select
value={selectedGenre}
onValueChange={setSelectedGenre}
>
<SelectTrigger className="bg-slate-800 border-slate-700"> <SelectTrigger className="bg-slate-800 border-slate-700">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
@ -162,14 +172,21 @@ export default function TrackLibrary() {
<label className="text-xs uppercase text-slate-500 mb-2 block"> <label className="text-xs uppercase text-slate-500 mb-2 block">
License Type License Type
</label> </label>
<Select value={licenseFilter} onValueChange={setLicenseFilter}> <Select
value={licenseFilter}
onValueChange={setLicenseFilter}
>
<SelectTrigger className="bg-slate-800 border-slate-700"> <SelectTrigger className="bg-slate-800 border-slate-700">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent className="bg-slate-800 border-slate-700"> <SelectContent className="bg-slate-800 border-slate-700">
<SelectItem value="all">All Licenses</SelectItem> <SelectItem value="all">All Licenses</SelectItem>
<SelectItem value="ecosystem">Ecosystem Free</SelectItem> <SelectItem value="ecosystem">
<SelectItem value="commercial_sample">Commercial Demo</SelectItem> Ecosystem Free
</SelectItem>
<SelectItem value="commercial_sample">
Commercial Demo
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@ -207,7 +224,9 @@ export default function TrackLibrary() {
) : tracks.length === 0 ? ( ) : tracks.length === 0 ? (
<div className="text-center py-12"> <div className="text-center py-12">
<Music className="h-12 w-12 text-slate-600 mx-auto mb-4" /> <Music className="h-12 w-12 text-slate-600 mx-auto mb-4" />
<p className="text-slate-400">No tracks found. Try adjusting your filters.</p> <p className="text-slate-400">
No tracks found. Try adjusting your filters.
</p>
</div> </div>
) : ( ) : (
<div className="grid gap-4"> <div className="grid gap-4">
@ -241,7 +260,9 @@ export default function TrackLibrary() {
: "bg-blue-500/10 border-blue-500/30" : "bg-blue-500/10 border-blue-500/30"
} }
> >
{track.license_type === "ecosystem" ? "Free" : "Commercial"} {track.license_type === "ecosystem"
? "Free"
: "Commercial"}
</Badge> </Badge>
</div> </div>

View file

@ -72,7 +72,9 @@ export default function FoundationCurriculum() {
if (selectedCategory) params.set("category", selectedCategory); if (selectedCategory) params.set("category", selectedCategory);
if (selectedDifficulty) params.set("difficulty", selectedDifficulty); if (selectedDifficulty) params.set("difficulty", selectedDifficulty);
const response = await fetch(`${API_BASE}/api/foundation/courses?${params}`); const response = await fetch(
`${API_BASE}/api/foundation/courses?${params}`,
);
if (!response.ok) throw new Error("Failed to fetch courses"); if (!response.ok) throw new Error("Failed to fetch courses");
let data = await response.json(); let data = await response.json();