@nordsym/apiclaw 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +303 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/docs/PRD-workspace-fixes.md +178 -0
- package/landing/src/app/dashboard/page.tsx +9 -679
- package/landing/src/app/dashboard/verify/page.tsx +1 -1
- package/landing/src/app/login/page.tsx +1 -1
- package/landing/src/app/page.tsx +23 -7
- package/landing/src/app/providers/dashboard/layout.tsx +5 -4
- package/landing/src/app/providers/dashboard/page.tsx +11 -641
- package/landing/src/app/workspace/layout.tsx +30 -0
- package/landing/src/app/workspace/page.tsx +1637 -0
- package/landing/src/lib/stats.json +1 -1
- package/package.json +1 -1
- package/src/cli.ts +370 -0
- package/src/index.ts +10 -0
|
@@ -1,652 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
BarChart3,
|
|
7
|
-
CreditCard,
|
|
8
|
-
Settings,
|
|
9
|
-
TrendingUp,
|
|
10
|
-
Users,
|
|
11
|
-
Zap,
|
|
12
|
-
DollarSign,
|
|
13
|
-
ArrowUpRight,
|
|
14
|
-
ArrowDownRight,
|
|
15
|
-
ExternalLink,
|
|
16
|
-
ChevronRight,
|
|
17
|
-
Check,
|
|
18
|
-
Clock,
|
|
19
|
-
AlertCircle,
|
|
20
|
-
LogOut,
|
|
21
|
-
Loader2,
|
|
22
|
-
RefreshCw,
|
|
23
|
-
Plus,
|
|
24
|
-
Rocket,
|
|
25
|
-
X,
|
|
26
|
-
} from "lucide-react";
|
|
27
|
-
import {
|
|
28
|
-
LineChart,
|
|
29
|
-
Line,
|
|
30
|
-
BarChart,
|
|
31
|
-
Bar,
|
|
32
|
-
XAxis,
|
|
33
|
-
YAxis,
|
|
34
|
-
CartesianGrid,
|
|
35
|
-
Tooltip,
|
|
36
|
-
ResponsiveContainer,
|
|
37
|
-
PieChart,
|
|
38
|
-
Pie,
|
|
39
|
-
Cell,
|
|
40
|
-
} from "recharts";
|
|
41
|
-
import Link from "next/link";
|
|
42
|
-
import { useDashboardData } from "@/hooks/useDashboardData";
|
|
43
|
-
import type { ProviderAPI, Analytics, Earnings } from "@/lib/convex-client";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { Loader2 } from "lucide-react";
|
|
44
6
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const COLORS = ["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6"];
|
|
48
|
-
|
|
49
|
-
export default function DashboardPage() {
|
|
50
|
-
const { session, apis, analytics, earnings, isLoading, error, refresh, logout } = useDashboardData();
|
|
51
|
-
const searchParams = useSearchParams();
|
|
52
|
-
const tabFromUrl = searchParams.get("tab") as TabType | null;
|
|
53
|
-
const [activeTab, setActiveTab] = useState<TabType>(tabFromUrl || "overview");
|
|
7
|
+
export default function ProviderDashboardRedirect() {
|
|
8
|
+
const router = useRouter();
|
|
54
9
|
|
|
55
10
|
useEffect(() => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
} else if (!tabFromUrl) {
|
|
59
|
-
setActiveTab("overview");
|
|
60
|
-
}
|
|
61
|
-
}, [tabFromUrl]);
|
|
62
|
-
|
|
63
|
-
const tabs = [
|
|
64
|
-
{ id: "overview" as TabType, label: "Overview", icon: BarChart3 },
|
|
65
|
-
{ id: "apis" as TabType, label: "APIs", icon: Zap },
|
|
66
|
-
{ id: "analytics" as TabType, label: "Analytics", icon: TrendingUp },
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
if (isLoading) {
|
|
70
|
-
return (
|
|
71
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
72
|
-
<div className="text-center">
|
|
73
|
-
<Loader2 className="w-12 h-12 text-accent animate-spin mx-auto mb-4" />
|
|
74
|
-
<p className="text-text-muted">Loading dashboard...</p>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (error) {
|
|
81
|
-
return (
|
|
82
|
-
<div className="min-h-screen flex items-center justify-center px-6">
|
|
83
|
-
<div className="text-center max-w-md">
|
|
84
|
-
<AlertCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
|
85
|
-
<h1 className="text-2xl font-bold mb-2">Something Went Wrong</h1>
|
|
86
|
-
<p className="text-text-muted mb-6">{error}</p>
|
|
87
|
-
<button onClick={refresh} className="btn-primary">
|
|
88
|
-
<RefreshCw className="w-5 h-5" />
|
|
89
|
-
Try Again
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!session) {
|
|
97
|
-
return null; // Will redirect to login via hook
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<div className="min-h-screen bg-background">
|
|
102
|
-
{/* Header */}
|
|
103
|
-
<header className="border-b border-border bg-surface/50 backdrop-blur-xl sticky top-0 z-50">
|
|
104
|
-
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
105
|
-
<div className="flex items-center gap-4">
|
|
106
|
-
<Link href="/" className="w-10 h-10 rounded-xl bg-accent/20 flex items-center justify-center text-xl">
|
|
107
|
-
🦞
|
|
108
|
-
</Link>
|
|
109
|
-
<div>
|
|
110
|
-
<h1 className="font-bold text-lg">Provider Dashboard</h1>
|
|
111
|
-
<p className="text-sm text-text-muted">{session.name || session.email}</p>
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
<div className="flex items-center gap-3">
|
|
115
|
-
<button
|
|
116
|
-
onClick={refresh}
|
|
117
|
-
className="p-2 rounded-lg hover:bg-surface transition"
|
|
118
|
-
title="Refresh"
|
|
119
|
-
>
|
|
120
|
-
<RefreshCw className="w-5 h-5 text-text-muted" />
|
|
121
|
-
</button>
|
|
122
|
-
<button
|
|
123
|
-
onClick={logout}
|
|
124
|
-
className="p-2 rounded-lg hover:bg-surface transition"
|
|
125
|
-
title="Sign out"
|
|
126
|
-
>
|
|
127
|
-
<LogOut className="w-5 h-5 text-text-muted" />
|
|
128
|
-
</button>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
</header>
|
|
132
|
-
|
|
133
|
-
<div className="max-w-7xl mx-auto px-6 py-8">
|
|
134
|
-
{/* Tab Navigation */}
|
|
135
|
-
<div className="flex items-center gap-1 p-1 bg-surface rounded-xl w-fit mb-8">
|
|
136
|
-
{tabs.map((tab) => (
|
|
137
|
-
<button
|
|
138
|
-
key={tab.id}
|
|
139
|
-
onClick={() => setActiveTab(tab.id)}
|
|
140
|
-
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${
|
|
141
|
-
activeTab === tab.id
|
|
142
|
-
? "bg-accent text-white"
|
|
143
|
-
: "text-text-muted hover:text-text-primary"
|
|
144
|
-
}`}
|
|
145
|
-
>
|
|
146
|
-
<tab.icon className="w-4 h-4" />
|
|
147
|
-
{tab.label}
|
|
148
|
-
</button>
|
|
149
|
-
))}
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
{/* Tab Content */}
|
|
153
|
-
{activeTab === "overview" && (
|
|
154
|
-
<OverviewTab apis={apis} analytics={analytics} />
|
|
155
|
-
)}
|
|
156
|
-
{activeTab === "apis" && <ApisTab apis={apis} />}
|
|
157
|
-
{activeTab === "analytics" && <UsageTab apis={apis} analytics={analytics} />}
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ============================================
|
|
164
|
-
// OVERVIEW TAB
|
|
165
|
-
// ============================================
|
|
166
|
-
|
|
167
|
-
function OverviewTab({
|
|
168
|
-
apis,
|
|
169
|
-
analytics,
|
|
170
|
-
}: {
|
|
171
|
-
apis: ProviderAPI[];
|
|
172
|
-
analytics: Analytics | null;
|
|
173
|
-
}) {
|
|
174
|
-
const totalCalls = analytics?.totalCalls || 0;
|
|
175
|
-
const totalDiscoveries = apis.reduce((sum, a) => sum + (a.discoveryCount || 0), 0);
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<div className="space-y-8">
|
|
179
|
-
<h2 className="text-2xl font-bold">Overview</h2>
|
|
180
|
-
|
|
181
|
-
{/* Quick Stats */}
|
|
182
|
-
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
183
|
-
<div className="rounded-2xl border border-accent/30 bg-accent/10 p-6">
|
|
184
|
-
<div className="flex items-center gap-3 mb-3">
|
|
185
|
-
<Zap className="w-6 h-6 text-accent" />
|
|
186
|
-
<span className="text-text-muted">Listed APIs</span>
|
|
187
|
-
</div>
|
|
188
|
-
<p className="text-4xl font-bold text-accent">{apis.length}</p>
|
|
189
|
-
</div>
|
|
190
|
-
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
191
|
-
<div className="flex items-center gap-3 mb-3">
|
|
192
|
-
<TrendingUp className="w-6 h-6 text-text-muted" />
|
|
193
|
-
<span className="text-text-muted">Total Calls</span>
|
|
194
|
-
</div>
|
|
195
|
-
<p className="text-4xl font-bold">{totalCalls.toLocaleString()}</p>
|
|
196
|
-
</div>
|
|
197
|
-
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
198
|
-
<div className="flex items-center gap-3 mb-3">
|
|
199
|
-
<Users className="w-6 h-6 text-text-muted" />
|
|
200
|
-
<span className="text-text-muted">Discoveries</span>
|
|
201
|
-
</div>
|
|
202
|
-
<p className="text-4xl font-bold">{totalDiscoveries}</p>
|
|
203
|
-
</div>
|
|
204
|
-
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
205
|
-
<div className="flex items-center gap-3 mb-3">
|
|
206
|
-
<Check className="w-6 h-6 text-green-500" />
|
|
207
|
-
<span className="text-text-muted">Status</span>
|
|
208
|
-
</div>
|
|
209
|
-
<p className="text-xl font-bold text-green-500">Active</p>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
{/* Your APIs */}
|
|
214
|
-
<div>
|
|
215
|
-
<div className="flex items-center justify-between mb-4">
|
|
216
|
-
<h3 className="font-bold text-lg">Your APIs</h3>
|
|
217
|
-
<Link href="/providers/register" className="btn-secondary !py-2 !px-4 text-sm">
|
|
218
|
-
<Plus className="w-4 h-4" />
|
|
219
|
-
Add API
|
|
220
|
-
</Link>
|
|
221
|
-
</div>
|
|
222
|
-
<div className="grid gap-4">
|
|
223
|
-
{apis.map((api) => (
|
|
224
|
-
<Link key={api._id} href={`/providers/dashboard/${api._id}`} className="block rounded-xl border border-border bg-surface-elevated p-5 hover:border-accent/50 transition cursor-pointer">
|
|
225
|
-
<div className="flex items-center justify-between mb-2">
|
|
226
|
-
<h4 className="font-semibold">{api.name}</h4>
|
|
227
|
-
<div className="flex items-center gap-2">
|
|
228
|
-
{api.hasDirectCall && (
|
|
229
|
-
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
230
|
-
api.directCallStatus === "live" ? "bg-cyan-500/20 text-cyan-500" : "bg-purple-500/20 text-purple-500"
|
|
231
|
-
}`}>
|
|
232
|
-
⚡ Direct Call
|
|
233
|
-
</span>
|
|
234
|
-
)}
|
|
235
|
-
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
236
|
-
api.status === "approved" ? "bg-green-500/20 text-green-500" : "bg-yellow-500/20 text-yellow-600"
|
|
237
|
-
}`}>
|
|
238
|
-
{api.status}
|
|
239
|
-
</span>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
<p className="text-text-muted text-sm line-clamp-2 mb-3">{api.description}</p>
|
|
243
|
-
<div className="flex items-center gap-4 text-sm text-text-muted">
|
|
244
|
-
<span>{api.category}</span>
|
|
245
|
-
<span>{api.discoveryCount || 0} discoveries</span>
|
|
246
|
-
{api.docsUrl && (
|
|
247
|
-
<a href={api.docsUrl} target="_blank" rel="noopener noreferrer" className="text-accent hover:underline flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
|
|
248
|
-
Docs <ExternalLink className="w-3 h-3" />
|
|
249
|
-
</a>
|
|
250
|
-
)}
|
|
251
|
-
</div>
|
|
252
|
-
</Link>
|
|
253
|
-
))}
|
|
254
|
-
{apis.length === 0 && (
|
|
255
|
-
<div className="text-center py-12 rounded-xl border border-dashed border-border">
|
|
256
|
-
<p className="text-text-muted mb-4">No APIs listed yet</p>
|
|
257
|
-
<Link href="/providers/register" className="btn-primary">
|
|
258
|
-
<Plus className="w-5 h-5" />
|
|
259
|
-
List Your First API
|
|
260
|
-
</Link>
|
|
261
|
-
</div>
|
|
262
|
-
)}
|
|
263
|
-
</div>
|
|
264
|
-
</div>
|
|
265
|
-
|
|
266
|
-
{/* Quick Actions */}
|
|
267
|
-
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
268
|
-
<h3 className="font-bold text-lg mb-4">Quick Actions</h3>
|
|
269
|
-
<div className="grid md:grid-cols-3 gap-4">
|
|
270
|
-
<Link href="/providers/register" className="flex items-center gap-3 p-4 rounded-xl border border-border hover:border-accent/50 transition">
|
|
271
|
-
<Plus className="w-8 h-8 text-accent" />
|
|
272
|
-
<div>
|
|
273
|
-
<p className="font-medium">Add API</p>
|
|
274
|
-
<p className="text-sm text-text-muted">List a new API</p>
|
|
275
|
-
</div>
|
|
276
|
-
</Link>
|
|
277
|
-
<button
|
|
278
|
-
onClick={() => window.location.href = '/providers/dashboard?tab=analytics'}
|
|
279
|
-
className="flex items-center gap-3 p-4 rounded-xl border border-border hover:border-accent/50 transition text-left"
|
|
280
|
-
>
|
|
281
|
-
<BarChart3 className="w-8 h-8 text-accent" />
|
|
282
|
-
<div>
|
|
283
|
-
<p className="font-medium">View Usage</p>
|
|
284
|
-
<p className="text-sm text-text-muted">Detailed analytics</p>
|
|
285
|
-
</div>
|
|
286
|
-
</button>
|
|
287
|
-
<a href="https://github.com/nordsym/apiclaw" target="_blank" rel="noopener noreferrer" className="flex items-center gap-3 p-4 rounded-xl border border-border hover:border-accent/50 transition">
|
|
288
|
-
<ExternalLink className="w-8 h-8 text-accent" />
|
|
289
|
-
<div>
|
|
290
|
-
<p className="font-medium">Documentation</p>
|
|
291
|
-
<p className="text-sm text-text-muted">Integration guides</p>
|
|
292
|
-
</div>
|
|
293
|
-
</a>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function StatCard({
|
|
301
|
-
title,
|
|
302
|
-
value,
|
|
303
|
-
change,
|
|
304
|
-
icon: Icon,
|
|
305
|
-
accent,
|
|
306
|
-
}: {
|
|
307
|
-
title: string;
|
|
308
|
-
value: string;
|
|
309
|
-
change?: number;
|
|
310
|
-
icon: typeof Zap;
|
|
311
|
-
accent?: boolean;
|
|
312
|
-
}) {
|
|
313
|
-
return (
|
|
314
|
-
<div className={`rounded-2xl border p-5 ${accent ? "bg-accent/10 border-accent/30" : "bg-surface-elevated border-border"}`}>
|
|
315
|
-
<div className="flex items-center justify-between mb-3">
|
|
316
|
-
<span className="text-sm text-text-muted">{title}</span>
|
|
317
|
-
<Icon className={`w-5 h-5 ${accent ? "text-accent" : "text-text-muted"}`} />
|
|
318
|
-
</div>
|
|
319
|
-
<div className="flex items-end justify-between">
|
|
320
|
-
<span className={`text-3xl font-bold ${accent ? "text-accent" : ""}`}>{value}</span>
|
|
321
|
-
{change !== undefined && (
|
|
322
|
-
<div className={`flex items-center gap-1 text-sm ${change >= 0 ? "text-green-500" : "text-red-500"}`}>
|
|
323
|
-
{change >= 0 ? <ArrowUpRight className="w-4 h-4" /> : <ArrowDownRight className="w-4 h-4" />}
|
|
324
|
-
{Math.abs(change).toFixed(1)}%
|
|
325
|
-
</div>
|
|
326
|
-
)}
|
|
327
|
-
</div>
|
|
328
|
-
</div>
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// ============================================
|
|
333
|
-
// APIS TAB
|
|
334
|
-
// ============================================
|
|
335
|
-
|
|
336
|
-
function ApisTab({ apis, onDelete }: { apis: ProviderAPI[], onDelete?: (apiId: string) => void }) {
|
|
337
|
-
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
|
|
338
|
-
const [deleting, setDeleting] = useState(false);
|
|
339
|
-
|
|
340
|
-
const handleDelete = async (apiId: string, apiName: string) => {
|
|
341
|
-
if (deleteConfirm !== apiId) {
|
|
342
|
-
setDeleteConfirm(apiId);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
setDeleting(true);
|
|
347
|
-
try {
|
|
348
|
-
const token = localStorage.getItem("apiclaw_session");
|
|
349
|
-
if (!token) return;
|
|
350
|
-
|
|
351
|
-
const response = await fetch(`${process.env.NEXT_PUBLIC_CONVEX_URL || 'https://adventurous-avocet-799.convex.cloud'}/api/mutation`, {
|
|
352
|
-
method: 'POST',
|
|
353
|
-
headers: { 'Content-Type': 'application/json' },
|
|
354
|
-
body: JSON.stringify({
|
|
355
|
-
path: 'providers:deleteAPI',
|
|
356
|
-
args: { token, apiId }
|
|
357
|
-
})
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
if (response.ok) {
|
|
361
|
-
onDelete?.(apiId);
|
|
362
|
-
window.location.reload();
|
|
363
|
-
}
|
|
364
|
-
} catch (error) {
|
|
365
|
-
console.error('Delete failed:', error);
|
|
366
|
-
} finally {
|
|
367
|
-
setDeleting(false);
|
|
368
|
-
setDeleteConfirm(null);
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
return (
|
|
373
|
-
<div className="space-y-6">
|
|
374
|
-
<div className="flex items-center justify-between">
|
|
375
|
-
<h2 className="text-2xl font-bold">Your APIs</h2>
|
|
376
|
-
<Link href="/providers/register" className="btn-primary !py-2 !px-4 text-sm">
|
|
377
|
-
<Plus className="w-4 h-4" />
|
|
378
|
-
Add API
|
|
379
|
-
</Link>
|
|
380
|
-
</div>
|
|
381
|
-
|
|
382
|
-
{apis.length === 0 ? (
|
|
383
|
-
<div className="text-center py-16 rounded-2xl border border-dashed border-border bg-surface/50">
|
|
384
|
-
<Zap className="w-12 h-12 text-text-muted mx-auto mb-4" />
|
|
385
|
-
<h3 className="font-semibold text-lg mb-2">No APIs Listed</h3>
|
|
386
|
-
<p className="text-text-muted mb-6">List your first API to make it discoverable by AI agents.</p>
|
|
387
|
-
<Link href="/providers/register" className="btn-primary">
|
|
388
|
-
<Plus className="w-5 h-5" />
|
|
389
|
-
List Your First API
|
|
390
|
-
</Link>
|
|
391
|
-
</div>
|
|
392
|
-
) : (
|
|
393
|
-
<div className="grid gap-4">
|
|
394
|
-
{apis.map((api) => (
|
|
395
|
-
<div key={api._id} className="relative rounded-2xl border border-border bg-surface-elevated p-6 hover:border-accent/50 transition">
|
|
396
|
-
{/* Delete button */}
|
|
397
|
-
<button
|
|
398
|
-
onClick={(e) => {
|
|
399
|
-
e.preventDefault();
|
|
400
|
-
e.stopPropagation();
|
|
401
|
-
handleDelete(api._id, api.name);
|
|
402
|
-
}}
|
|
403
|
-
className={`absolute top-4 right-4 p-2 rounded-lg transition ${
|
|
404
|
-
deleteConfirm === api._id
|
|
405
|
-
? 'bg-red-500 text-white'
|
|
406
|
-
: 'hover:bg-red-500/20 text-text-muted hover:text-red-500'
|
|
407
|
-
}`}
|
|
408
|
-
title={deleteConfirm === api._id ? 'Click again to confirm' : 'Delete API'}
|
|
409
|
-
>
|
|
410
|
-
{deleting && deleteConfirm === api._id ? (
|
|
411
|
-
<Loader2 className="w-4 h-4 animate-spin" />
|
|
412
|
-
) : (
|
|
413
|
-
<X className="w-4 h-4" />
|
|
414
|
-
)}
|
|
415
|
-
</button>
|
|
416
|
-
{deleteConfirm === api._id && (
|
|
417
|
-
<div className="absolute top-14 right-4 bg-surface-elevated border border-red-500/50 rounded-lg px-3 py-2 text-sm text-red-500 shadow-lg">
|
|
418
|
-
Click again to delete
|
|
419
|
-
</div>
|
|
420
|
-
)}
|
|
421
|
-
|
|
422
|
-
<Link href={`/providers/dashboard/${api._id}`} className="block cursor-pointer">
|
|
423
|
-
<div className="flex items-center justify-between mb-3 pr-10">
|
|
424
|
-
<div className="flex items-center gap-3">
|
|
425
|
-
<Zap className="w-8 h-8 text-accent" />
|
|
426
|
-
<div>
|
|
427
|
-
<h3 className="font-semibold text-lg">{api.name}</h3>
|
|
428
|
-
<span className="text-sm text-text-muted">{api.category}</span>
|
|
429
|
-
</div>
|
|
430
|
-
</div>
|
|
431
|
-
<div className="flex items-center gap-2">
|
|
432
|
-
{api.hasDirectCall && (
|
|
433
|
-
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
|
434
|
-
api.directCallStatus === "live" ? "bg-cyan-500/20 text-cyan-500" : "bg-purple-500/20 text-purple-500"
|
|
435
|
-
}`}>
|
|
436
|
-
⚡ Direct Call
|
|
437
|
-
</span>
|
|
438
|
-
)}
|
|
439
|
-
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
|
440
|
-
api.status === "approved"
|
|
441
|
-
? "bg-green-500/20 text-green-500"
|
|
442
|
-
: api.status === "pending"
|
|
443
|
-
? "bg-yellow-500/20 text-yellow-600"
|
|
444
|
-
: "bg-gray-500/20 text-gray-500"
|
|
445
|
-
}`}>
|
|
446
|
-
{api.status}
|
|
447
|
-
</span>
|
|
448
|
-
</div>
|
|
449
|
-
</div>
|
|
450
|
-
<p className="text-text-secondary mb-4">{api.description}</p>
|
|
451
|
-
<div className="flex items-center gap-6 text-sm">
|
|
452
|
-
<div>
|
|
453
|
-
<span className="text-text-muted">Pricing:</span>{" "}
|
|
454
|
-
<span className="capitalize">{api.pricingModel}</span>
|
|
455
|
-
</div>
|
|
456
|
-
<div>
|
|
457
|
-
<span className="text-text-muted">Discoveries:</span>{" "}
|
|
458
|
-
<span>{api.discoveryCount || 0}</span>
|
|
459
|
-
</div>
|
|
460
|
-
{api.docsUrl && (
|
|
461
|
-
<a
|
|
462
|
-
href={api.docsUrl}
|
|
463
|
-
target="_blank"
|
|
464
|
-
rel="noopener noreferrer"
|
|
465
|
-
className="text-accent hover:underline flex items-center gap-1"
|
|
466
|
-
onClick={(e) => e.stopPropagation()}
|
|
467
|
-
>
|
|
468
|
-
Documentation <ExternalLink className="w-3 h-3" />
|
|
469
|
-
</a>
|
|
470
|
-
)}
|
|
471
|
-
</div>
|
|
472
|
-
</Link>
|
|
473
|
-
</div>
|
|
474
|
-
))}
|
|
475
|
-
</div>
|
|
476
|
-
)}
|
|
477
|
-
</div>
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// ============================================
|
|
482
|
-
// ANALYTICS TAB
|
|
483
|
-
// ============================================
|
|
484
|
-
|
|
485
|
-
function UsageTab({ apis, analytics }: { apis: ProviderAPI[]; analytics: Analytics | null }) {
|
|
486
|
-
const totalCalls = analytics?.totalCalls || 0;
|
|
487
|
-
const uniqueAgents = analytics?.uniqueAgents || 0;
|
|
488
|
-
const totalDiscoveries = apis.reduce((sum, a) => sum + (a.discoveryCount || 0), 0);
|
|
489
|
-
const hasChartData = analytics && analytics.callsByDay && analytics.callsByDay.length > 0;
|
|
11
|
+
router.replace("/workspace?tab=apis");
|
|
12
|
+
}, [router]);
|
|
490
13
|
|
|
491
14
|
return (
|
|
492
|
-
<div className="
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
<
|
|
496
|
-
<AlertCircle className="w-5 h-5 text-accent flex-shrink-0" />
|
|
497
|
-
<div>
|
|
498
|
-
<p className="font-medium text-accent">Preview Mode</p>
|
|
499
|
-
<p className="text-sm text-text-muted">This is sample data. Real analytics will appear once agents start using your API.</p>
|
|
500
|
-
</div>
|
|
501
|
-
</div>
|
|
502
|
-
)}
|
|
503
|
-
|
|
504
|
-
<h2 className="text-2xl font-bold">Analytics</h2>
|
|
505
|
-
|
|
506
|
-
{/* Stats Grid */}
|
|
507
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
508
|
-
<StatCard
|
|
509
|
-
title="Total Calls"
|
|
510
|
-
value={totalCalls.toLocaleString()}
|
|
511
|
-
icon={Zap}
|
|
512
|
-
accent
|
|
513
|
-
/>
|
|
514
|
-
<StatCard
|
|
515
|
-
title="Unique Agents"
|
|
516
|
-
value={uniqueAgents.toString()}
|
|
517
|
-
icon={Users}
|
|
518
|
-
/>
|
|
519
|
-
<StatCard
|
|
520
|
-
title="Avg Latency"
|
|
521
|
-
value={`${analytics?.avgLatency || 145}ms`}
|
|
522
|
-
icon={Clock}
|
|
523
|
-
/>
|
|
524
|
-
<StatCard
|
|
525
|
-
title="Success Rate"
|
|
526
|
-
value={`${(analytics?.successRate || 98.2).toFixed(1)}%`}
|
|
527
|
-
icon={Check}
|
|
528
|
-
/>
|
|
15
|
+
<div className="min-h-screen flex items-center justify-center bg-[var(--background)]">
|
|
16
|
+
<div className="text-center">
|
|
17
|
+
<Loader2 className="w-12 h-12 text-[#ef4444] animate-spin mx-auto mb-4" />
|
|
18
|
+
<p className="text-[var(--text-muted)]">Redirecting to workspace...</p>
|
|
529
19
|
</div>
|
|
530
|
-
|
|
531
|
-
{/* Charts */}
|
|
532
|
-
{hasChartData && (
|
|
533
|
-
<div className="grid lg:grid-cols-3 gap-6">
|
|
534
|
-
{/* Line Chart - Calls Over Time */}
|
|
535
|
-
<div className="lg:col-span-2 bg-surface-elevated rounded-2xl border border-border p-6">
|
|
536
|
-
<h3 className="font-semibold mb-4">Calls Over Time</h3>
|
|
537
|
-
<div className="h-80">
|
|
538
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
539
|
-
<LineChart data={analytics!.callsByDay}>
|
|
540
|
-
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
|
|
541
|
-
<XAxis
|
|
542
|
-
dataKey="date"
|
|
543
|
-
tick={{ fontSize: 12, fill: "var(--text-muted)" }}
|
|
544
|
-
tickFormatter={(d) => new Date(d).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
|
|
545
|
-
/>
|
|
546
|
-
<YAxis tick={{ fontSize: 12, fill: "var(--text-muted)" }} />
|
|
547
|
-
<Tooltip
|
|
548
|
-
contentStyle={{
|
|
549
|
-
background: "var(--surface-elevated)",
|
|
550
|
-
border: "1px solid var(--border)",
|
|
551
|
-
borderRadius: "8px",
|
|
552
|
-
}}
|
|
553
|
-
labelFormatter={(d) => new Date(d).toLocaleDateString()}
|
|
554
|
-
/>
|
|
555
|
-
<Line
|
|
556
|
-
type="monotone"
|
|
557
|
-
dataKey="calls"
|
|
558
|
-
stroke="#ef4444"
|
|
559
|
-
strokeWidth={2}
|
|
560
|
-
dot={false}
|
|
561
|
-
activeDot={{ r: 4, fill: "#ef4444" }}
|
|
562
|
-
/>
|
|
563
|
-
</LineChart>
|
|
564
|
-
</ResponsiveContainer>
|
|
565
|
-
</div>
|
|
566
|
-
</div>
|
|
567
|
-
|
|
568
|
-
{/* Top Agents */}
|
|
569
|
-
<div className="bg-surface-elevated rounded-2xl border border-border p-6">
|
|
570
|
-
<h3 className="font-semibold mb-4">Top Agents</h3>
|
|
571
|
-
<div className="space-y-3">
|
|
572
|
-
{analytics!.topAgents.slice(0, 6).map((agent, i) => (
|
|
573
|
-
<div key={agent.agentId} className="flex items-center justify-between">
|
|
574
|
-
<div className="flex items-center gap-3">
|
|
575
|
-
<span className="w-6 h-6 rounded-full bg-surface flex items-center justify-center text-xs font-medium text-text-muted">
|
|
576
|
-
{i + 1}
|
|
577
|
-
</span>
|
|
578
|
-
<span className="text-sm font-mono truncate max-w-[140px]">
|
|
579
|
-
{agent.agentId.replace("agent_", "")}
|
|
580
|
-
</span>
|
|
581
|
-
</div>
|
|
582
|
-
<span className="text-sm text-text-muted">{agent.calls.toLocaleString()}</span>
|
|
583
|
-
</div>
|
|
584
|
-
))}
|
|
585
|
-
{analytics!.topAgents.length === 0 && (
|
|
586
|
-
<p className="text-text-muted text-sm">No agent activity yet</p>
|
|
587
|
-
)}
|
|
588
|
-
</div>
|
|
589
|
-
</div>
|
|
590
|
-
</div>
|
|
591
|
-
)}
|
|
592
|
-
|
|
593
|
-
{/* Top Actions */}
|
|
594
|
-
{analytics?.topActions && analytics.topActions.length > 0 && (
|
|
595
|
-
<div className="bg-surface-elevated rounded-2xl border border-border p-6">
|
|
596
|
-
<h3 className="font-semibold mb-4">Top Actions</h3>
|
|
597
|
-
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
598
|
-
{analytics.topActions.slice(0, 6).map((action, i) => (
|
|
599
|
-
<div key={action.actionName} className="flex items-center justify-between p-3 rounded-lg bg-surface">
|
|
600
|
-
<div className="flex items-center gap-3">
|
|
601
|
-
<span className="w-6 h-6 rounded-full bg-accent/20 text-accent flex items-center justify-center text-xs font-medium">
|
|
602
|
-
{i + 1}
|
|
603
|
-
</span>
|
|
604
|
-
<span className="text-sm font-mono">{action.actionName}</span>
|
|
605
|
-
</div>
|
|
606
|
-
<span className="text-sm text-text-muted">{action.calls.toLocaleString()}</span>
|
|
607
|
-
</div>
|
|
608
|
-
))}
|
|
609
|
-
</div>
|
|
610
|
-
</div>
|
|
611
|
-
)}
|
|
612
|
-
|
|
613
|
-
{/* Usage by API */}
|
|
614
|
-
<div className="bg-surface-elevated border border-border rounded-2xl p-6">
|
|
615
|
-
<h3 className="font-semibold text-lg mb-4">Usage by API</h3>
|
|
616
|
-
{apis.length > 0 ? (
|
|
617
|
-
<div className="space-y-4">
|
|
618
|
-
{apis.map((api) => (
|
|
619
|
-
<div key={api._id} className="flex items-center justify-between p-4 rounded-xl bg-surface">
|
|
620
|
-
<div className="flex items-center gap-3">
|
|
621
|
-
<Zap className="w-5 h-5 text-accent" />
|
|
622
|
-
<div>
|
|
623
|
-
<p className="font-medium">{api.name}</p>
|
|
624
|
-
<p className="text-sm text-text-muted">{api.category}</p>
|
|
625
|
-
</div>
|
|
626
|
-
</div>
|
|
627
|
-
<div className="text-right">
|
|
628
|
-
<p className="font-semibold">{api.discoveryCount || 0} discoveries</p>
|
|
629
|
-
<p className="text-sm text-text-muted">
|
|
630
|
-
{api.status === "approved" ? "Live" : api.status}
|
|
631
|
-
</p>
|
|
632
|
-
</div>
|
|
633
|
-
</div>
|
|
634
|
-
))}
|
|
635
|
-
</div>
|
|
636
|
-
) : (
|
|
637
|
-
<p className="text-text-muted text-center py-8">No APIs listed yet</p>
|
|
638
|
-
)}
|
|
639
|
-
</div>
|
|
640
|
-
|
|
641
|
-
{totalCalls === 0 && !analytics?.isPreview && (
|
|
642
|
-
<div className="rounded-2xl border border-dashed border-border bg-surface/50 p-12 text-center">
|
|
643
|
-
<TrendingUp className="w-12 h-12 text-text-muted mx-auto mb-4" />
|
|
644
|
-
<h3 className="font-semibold text-lg mb-2">No Usage Yet</h3>
|
|
645
|
-
<p className="text-text-muted">
|
|
646
|
-
When agents start using your APIs, analytics stats will appear here.
|
|
647
|
-
</p>
|
|
648
|
-
</div>
|
|
649
|
-
)}
|
|
650
20
|
</div>
|
|
651
21
|
);
|
|
652
22
|
}
|