@nordsym/apiclaw 1.2.2 → 1.2.3

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.
Files changed (77) hide show
  1. package/AGENTS.md +50 -33
  2. package/README.md +22 -12
  3. package/SOUL.md +60 -19
  4. package/STATUS.md +91 -169
  5. package/convex/_generated/api.d.ts +6 -0
  6. package/convex/directCall.ts +598 -0
  7. package/convex/providers.ts +341 -26
  8. package/convex/schema.ts +87 -0
  9. package/convex/usage.ts +260 -0
  10. package/convex/waitlist.ts +55 -0
  11. package/data/combined-02-26.json +22102 -0
  12. package/data/night-expansion-02-26-06-batch2.json +1898 -0
  13. package/data/night-expansion-02-26-06-batch3.json +1410 -0
  14. package/data/night-expansion-02-26-06.json +3146 -0
  15. package/data/night-expansion-02-26-full.json +9726 -0
  16. package/data/night-expansion-02-26-v2.json +330 -0
  17. package/data/night-expansion-02-26.json +171 -0
  18. package/dist/crypto.d.ts +7 -0
  19. package/dist/crypto.d.ts.map +1 -0
  20. package/dist/crypto.js +67 -0
  21. package/dist/crypto.js.map +1 -0
  22. package/dist/execute-dynamic.d.ts +116 -0
  23. package/dist/execute-dynamic.d.ts.map +1 -0
  24. package/dist/execute-dynamic.js +456 -0
  25. package/dist/execute-dynamic.js.map +1 -0
  26. package/dist/execute.d.ts +2 -1
  27. package/dist/execute.d.ts.map +1 -1
  28. package/dist/execute.js +35 -5
  29. package/dist/execute.js.map +1 -1
  30. package/dist/index.js +33 -4
  31. package/dist/index.js.map +1 -1
  32. package/dist/registry/apis.json +2081 -3
  33. package/docs/PRD-customer-key-passthrough.md +184 -0
  34. package/landing/public/badges/available-on-apiclaw.svg +14 -0
  35. package/landing/scripts/generate-stats.js +75 -4
  36. package/landing/src/app/admin/page.tsx +1 -1
  37. package/landing/src/app/api/auth/magic-link/route.ts +1 -1
  38. package/landing/src/app/api/auth/session/route.ts +1 -1
  39. package/landing/src/app/api/auth/verify/route.ts +1 -1
  40. package/landing/src/app/api/og/route.tsx +5 -3
  41. package/landing/src/app/docs/page.tsx +5 -4
  42. package/landing/src/app/earn/page.tsx +14 -11
  43. package/landing/src/app/globals.css +16 -15
  44. package/landing/src/app/layout.tsx +2 -2
  45. package/landing/src/app/page.tsx +425 -254
  46. package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +600 -0
  47. package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +583 -0
  48. package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +301 -0
  49. package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +659 -0
  50. package/landing/src/app/providers/dashboard/[apiId]/page.tsx +381 -0
  51. package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +418 -0
  52. package/landing/src/app/providers/dashboard/layout.tsx +292 -0
  53. package/landing/src/app/providers/dashboard/page.tsx +353 -290
  54. package/landing/src/app/providers/register/page.tsx +87 -10
  55. package/landing/src/components/AiClientDropdown.tsx +85 -0
  56. package/landing/src/components/ConfigHelperModal.tsx +113 -0
  57. package/landing/src/components/HeroTabs.tsx +187 -0
  58. package/landing/src/components/ShareIntegrationModal.tsx +198 -0
  59. package/landing/src/hooks/useDashboardData.ts +53 -1
  60. package/landing/src/lib/apis.json +46554 -174
  61. package/landing/src/lib/convex-client.ts +22 -3
  62. package/landing/src/lib/stats.json +4 -4
  63. package/landing/tsconfig.tsbuildinfo +1 -1
  64. package/night-expansion-02-26-06-batch2.py +368 -0
  65. package/night-expansion-02-26-06-batch3.py +299 -0
  66. package/night-expansion-02-26-06.py +756 -0
  67. package/package.json +1 -1
  68. package/scripts/bulk-add-public-apis-v2.py +418 -0
  69. package/scripts/night-expansion-02-26-v2.py +296 -0
  70. package/scripts/night-expansion-02-26.py +890 -0
  71. package/scripts/seed-complete-api.js +181 -0
  72. package/scripts/seed-demo-api.sh +44 -0
  73. package/src/crypto.ts +75 -0
  74. package/src/execute-dynamic.ts +589 -0
  75. package/src/execute.ts +41 -5
  76. package/src/index.ts +38 -4
  77. package/src/registry/apis.json +2081 -3
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
3
+ import { useState, useEffect } from "react";
4
+ import { useSearchParams } from "next/navigation";
4
5
  import {
5
6
  BarChart3,
6
7
  CreditCard,
@@ -20,6 +21,8 @@ import {
20
21
  Loader2,
21
22
  RefreshCw,
22
23
  Plus,
24
+ Rocket,
25
+ X,
23
26
  } from "lucide-react";
24
27
  import {
25
28
  LineChart,
@@ -39,18 +42,28 @@ import Link from "next/link";
39
42
  import { useDashboardData } from "@/hooks/useDashboardData";
40
43
  import type { ProviderAPI, Analytics, Earnings } from "@/lib/convex-client";
41
44
 
42
- type TabType = "overview" | "apis" | "earnings";
45
+ type TabType = "overview" | "apis" | "analytics";
43
46
 
44
47
  const COLORS = ["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6"];
45
48
 
46
49
  export default function DashboardPage() {
47
50
  const { session, apis, analytics, earnings, isLoading, error, refresh, logout } = useDashboardData();
48
- const [activeTab, setActiveTab] = useState<TabType>("overview");
51
+ const searchParams = useSearchParams();
52
+ const tabFromUrl = searchParams.get("tab") as TabType | null;
53
+ const [activeTab, setActiveTab] = useState<TabType>(tabFromUrl || "overview");
54
+
55
+ useEffect(() => {
56
+ if (tabFromUrl && ["overview", "apis", "analytics"].includes(tabFromUrl)) {
57
+ setActiveTab(tabFromUrl);
58
+ } else if (!tabFromUrl) {
59
+ setActiveTab("overview");
60
+ }
61
+ }, [tabFromUrl]);
49
62
 
50
63
  const tabs = [
51
64
  { id: "overview" as TabType, label: "Overview", icon: BarChart3 },
52
65
  { id: "apis" as TabType, label: "APIs", icon: Zap },
53
- { id: "earnings" as TabType, label: "Earnings", icon: CreditCard },
66
+ { id: "analytics" as TabType, label: "Analytics", icon: TrendingUp },
54
67
  ];
55
68
 
56
69
  if (isLoading) {
@@ -141,7 +154,7 @@ export default function DashboardPage() {
141
154
  <OverviewTab apis={apis} analytics={analytics} />
142
155
  )}
143
156
  {activeTab === "apis" && <ApisTab apis={apis} />}
144
- {activeTab === "earnings" && <EarningsTab earnings={earnings} />}
157
+ {activeTab === "analytics" && <UsageTab apis={apis} analytics={analytics} />}
145
158
  </div>
146
159
  </div>
147
160
  );
@@ -158,201 +171,128 @@ function OverviewTab({
158
171
  apis: ProviderAPI[];
159
172
  analytics: Analytics | null;
160
173
  }) {
161
- const hasData = analytics && analytics.totalCalls > 0;
174
+ const totalCalls = analytics?.totalCalls || 0;
175
+ const totalDiscoveries = apis.reduce((sum, a) => sum + (a.discoveryCount || 0), 0);
162
176
 
163
- // If no analytics yet, show onboarding state
164
- if (!hasData) {
165
- return (
166
- <div className="space-y-8">
167
- <h2 className="text-2xl font-bold">Welcome to APIClaw!</h2>
168
-
169
- {/* Quick Stats */}
170
- <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
171
- <div className="rounded-2xl border border-border bg-surface-elevated p-6">
172
- <div className="flex items-center gap-3 mb-3">
173
- <Zap className="w-6 h-6 text-accent" />
174
- <span className="text-text-muted">Listed APIs</span>
175
- </div>
176
- <p className="text-4xl font-bold">{apis.length}</p>
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>
177
187
  </div>
178
- <div className="rounded-2xl border border-border bg-surface-elevated p-6">
179
- <div className="flex items-center gap-3 mb-3">
180
- <Users className="w-6 h-6 text-text-muted" />
181
- <span className="text-text-muted">Agent Discoveries</span>
182
- </div>
183
- <p className="text-4xl font-bold">
184
- {apis.reduce((sum, a) => sum + (a.discoveryCount || 0), 0)}
185
- </p>
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>
186
194
  </div>
187
- <div className="rounded-2xl border border-border bg-surface-elevated p-6">
188
- <div className="flex items-center gap-3 mb-3">
189
- <TrendingUp className="w-6 h-6 text-text-muted" />
190
- <span className="text-text-muted">Status</span>
191
- </div>
192
- <p className="text-xl font-bold text-green-500">Active</p>
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>
193
201
  </div>
202
+ <p className="text-4xl font-bold">{totalDiscoveries}</p>
194
203
  </div>
195
-
196
- {/* Getting Started */}
197
- <div className="rounded-2xl border border-accent/30 bg-accent/5 p-8">
198
- <h3 className="font-bold text-xl mb-4">🚀 Getting Started</h3>
199
- <p className="text-text-secondary mb-6">
200
- Your APIs are listed and discoverable by AI agents. Here&apos;s what happens next:
201
- </p>
202
- <ul className="space-y-3 text-text-secondary">
203
- <li className="flex items-start gap-3">
204
- <Check className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
205
- <span>AI agents can now find your APIs in the APIClaw registry</span>
206
- </li>
207
- <li className="flex items-start gap-3">
208
- <Check className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
209
- <span>When agents use your APIs, usage will appear here</span>
210
- </li>
211
- <li className="flex items-start gap-3">
212
- <Clock className="w-5 h-5 text-text-muted flex-shrink-0 mt-0.5" />
213
- <span>Set up Stripe Connect to receive payouts (coming soon)</span>
214
- </li>
215
- </ul>
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>
216
210
  </div>
211
+ </div>
217
212
 
218
- {/* Your APIs */}
219
- <div>
220
- <div className="flex items-center justify-between mb-4">
221
- <h3 className="font-bold text-lg">Your APIs</h3>
222
- <Link href="/providers/register" className="btn-secondary !py-2 !px-4 text-sm">
223
- <Plus className="w-4 h-4" />
224
- Add Another
225
- </Link>
226
- </div>
227
- <div className="grid gap-4">
228
- {apis.map((api) => (
229
- <div key={api._id} className="rounded-xl border border-border bg-surface-elevated p-5">
230
- <div className="flex items-center justify-between mb-2">
231
- <h4 className="font-semibold">{api.name}</h4>
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
+ )}
232
235
  <span className={`px-2 py-0.5 rounded-full text-xs font-medium ${
233
236
  api.status === "approved" ? "bg-green-500/20 text-green-500" : "bg-yellow-500/20 text-yellow-600"
234
237
  }`}>
235
238
  {api.status}
236
239
  </span>
237
240
  </div>
238
- <p className="text-text-muted text-sm line-clamp-2 mb-3">{api.description}</p>
239
- <div className="flex items-center gap-4 text-sm text-text-muted">
240
- <span>{api.category}</span>
241
- <span>{api.discoveryCount || 0} discoveries</span>
242
- {api.docsUrl && (
243
- <a href={api.docsUrl} target="_blank" rel="noopener noreferrer" className="text-accent hover:underline flex items-center gap-1">
244
- Docs <ExternalLink className="w-3 h-3" />
245
- </a>
246
- )}
247
- </div>
248
241
  </div>
249
- ))}
250
- {apis.length === 0 && (
251
- <div className="text-center py-12 rounded-xl border border-dashed border-border">
252
- <p className="text-text-muted mb-4">No APIs listed yet</p>
253
- <Link href="/providers/register" className="btn-primary">
254
- <Plus className="w-5 h-5" />
255
- List Your First API
256
- </Link>
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
+ )}
257
251
  </div>
258
- )}
259
- </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
+ )}
260
263
  </div>
261
264
  </div>
262
- );
263
- }
264
265
 
265
- // Has analytics data - show charts
266
- return (
267
- <div className="space-y-8">
268
- <h2 className="text-2xl font-bold">Analytics</h2>
269
-
270
- {/* Stats Grid */}
271
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
272
- <StatCard
273
- title="Total Calls"
274
- value={analytics.totalCalls.toLocaleString()}
275
- icon={Zap}
276
- />
277
- <StatCard
278
- title="Unique Agents"
279
- value={analytics.uniqueAgents.toString()}
280
- icon={Users}
281
- />
282
- <StatCard
283
- title="Revenue"
284
- value={`$${analytics.totalRevenue.toFixed(2)}`}
285
- icon={DollarSign}
286
- accent
287
- />
288
- <StatCard
289
- title="Listed APIs"
290
- value={apis.length.toString()}
291
- icon={TrendingUp}
292
- />
293
- </div>
294
-
295
- {/* Charts */}
296
- {analytics.callsByDay.length > 0 && (
297
- <div className="grid lg:grid-cols-3 gap-6">
298
- {/* Line Chart - Calls Over Time */}
299
- <div className="lg:col-span-2 bg-surface-elevated rounded-2xl border border-border p-6">
300
- <h3 className="font-semibold mb-4">Calls Over Time</h3>
301
- <div className="h-80">
302
- <ResponsiveContainer width="100%" height="100%">
303
- <LineChart data={analytics.callsByDay}>
304
- <CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
305
- <XAxis
306
- dataKey="date"
307
- tick={{ fontSize: 12, fill: "var(--text-muted)" }}
308
- tickFormatter={(d) => new Date(d).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
309
- />
310
- <YAxis tick={{ fontSize: 12, fill: "var(--text-muted)" }} />
311
- <Tooltip
312
- contentStyle={{
313
- background: "var(--surface-elevated)",
314
- border: "1px solid var(--border)",
315
- borderRadius: "8px",
316
- }}
317
- labelFormatter={(d) => new Date(d).toLocaleDateString()}
318
- />
319
- <Line
320
- type="monotone"
321
- dataKey="calls"
322
- stroke="#ef4444"
323
- strokeWidth={2}
324
- dot={false}
325
- activeDot={{ r: 4, fill: "#ef4444" }}
326
- />
327
- </LineChart>
328
- </ResponsiveContainer>
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>
329
275
  </div>
330
- </div>
331
-
332
- {/* Top Agents */}
333
- <div className="bg-surface-elevated rounded-2xl border border-border p-6">
334
- <h3 className="font-semibold mb-4">Top Agents</h3>
335
- <div className="space-y-3">
336
- {analytics.topAgents.slice(0, 6).map((agent, i) => (
337
- <div key={agent.agentId} className="flex items-center justify-between">
338
- <div className="flex items-center gap-3">
339
- <span className="w-6 h-6 rounded-full bg-surface flex items-center justify-center text-xs font-medium text-text-muted">
340
- {i + 1}
341
- </span>
342
- <span className="text-sm font-mono truncate max-w-[140px]">
343
- {agent.agentId.replace("agent_", "")}
344
- </span>
345
- </div>
346
- <span className="text-sm text-text-muted">{agent.calls.toLocaleString()}</span>
347
- </div>
348
- ))}
349
- {analytics.topAgents.length === 0 && (
350
- <p className="text-text-muted text-sm">No agent activity yet</p>
351
- )}
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>
352
285
  </div>
353
- </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>
354
294
  </div>
355
- )}
295
+ </div>
356
296
  </div>
357
297
  );
358
298
  }
@@ -393,7 +333,42 @@ function StatCard({
393
333
  // APIS TAB
394
334
  // ============================================
395
335
 
396
- function ApisTab({ apis }: { apis: ProviderAPI[] }) {
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
+
397
372
  return (
398
373
  <div className="space-y-6">
399
374
  <div className="flex items-center justify-between">
@@ -417,26 +392,62 @@ function ApisTab({ apis }: { apis: ProviderAPI[] }) {
417
392
  ) : (
418
393
  <div className="grid gap-4">
419
394
  {apis.map((api) => (
420
- <div key={api._id} className="rounded-2xl border border-border bg-surface-elevated p-6">
421
- <div className="flex items-center justify-between mb-3">
422
- <div className="flex items-center gap-3">
423
- <span className="text-3xl">🔌</span>
424
- <div>
425
- <h3 className="font-semibold text-lg">{api.name}</h3>
426
- <span className="text-sm text-text-muted">{api.category}</span>
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>
427
448
  </div>
428
449
  </div>
429
- <span className={`px-3 py-1 rounded-full text-sm font-medium ${
430
- api.status === "approved"
431
- ? "bg-green-500/20 text-green-500"
432
- : api.status === "pending"
433
- ? "bg-yellow-500/20 text-yellow-600"
434
- : "bg-gray-500/20 text-gray-500"
435
- }`}>
436
- {api.status}
437
- </span>
438
- </div>
439
- <p className="text-text-secondary mb-4">{api.description}</p>
450
+ <p className="text-text-secondary mb-4">{api.description}</p>
440
451
  <div className="flex items-center gap-6 text-sm">
441
452
  <div>
442
453
  <span className="text-text-muted">Pricing:</span>{" "}
@@ -452,11 +463,13 @@ function ApisTab({ apis }: { apis: ProviderAPI[] }) {
452
463
  target="_blank"
453
464
  rel="noopener noreferrer"
454
465
  className="text-accent hover:underline flex items-center gap-1"
466
+ onClick={(e) => e.stopPropagation()}
455
467
  >
456
468
  Documentation <ExternalLink className="w-3 h-3" />
457
469
  </a>
458
470
  )}
459
471
  </div>
472
+ </Link>
460
473
  </div>
461
474
  ))}
462
475
  </div>
@@ -466,121 +479,171 @@ function ApisTab({ apis }: { apis: ProviderAPI[] }) {
466
479
  }
467
480
 
468
481
  // ============================================
469
- // EARNINGS TAB
482
+ // ANALYTICS TAB
470
483
  // ============================================
471
484
 
472
- function EarningsTab({ earnings }: { earnings: Earnings | null }) {
473
- const hasEarnings = earnings && (earnings.totalEarned > 0 || earnings.payouts.length > 0);
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;
474
490
 
475
491
  return (
476
492
  <div className="space-y-8">
477
- <h2 className="text-2xl font-bold">Earnings</h2>
478
-
479
- {/* Earnings Stats */}
480
- <div className="grid md:grid-cols-3 gap-4">
481
- <div className="bg-accent/10 border border-accent/30 rounded-2xl p-6">
482
- <div className="flex items-center gap-2 mb-3">
483
- <Clock className="w-5 h-5 text-accent" />
484
- <span className="text-sm text-text-muted">Pending Payout</span>
485
- </div>
486
- <p className="text-4xl font-bold text-accent">${(earnings?.pendingAmount || 0).toFixed(2)}</p>
487
- <p className="text-sm text-text-muted mt-2">Available for payout</p>
488
- </div>
489
- <div className="bg-surface-elevated border border-border rounded-2xl p-6">
490
- <div className="flex items-center gap-2 mb-3">
491
- <DollarSign className="w-5 h-5 text-text-muted" />
492
- <span className="text-sm text-text-muted">Total Earned</span>
493
- </div>
494
- <p className="text-4xl font-bold">${(earnings?.totalEarned || 0).toFixed(2)}</p>
495
- <p className="text-sm text-text-muted mt-2">All time</p>
496
- </div>
497
- <div className="bg-surface-elevated border border-border rounded-2xl p-6">
498
- <div className="flex items-center gap-2 mb-3">
499
- <Check className="w-5 h-5 text-green-500" />
500
- <span className="text-sm text-text-muted">Total Paid Out</span>
493
+ {/* Preview Banner */}
494
+ {analytics?.isPreview && (
495
+ <div className="bg-accent/10 border border-accent/30 rounded-xl p-4 flex items-center gap-3">
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>
501
500
  </div>
502
- <p className="text-4xl font-bold">${(earnings?.totalPaidOut || 0).toFixed(2)}</p>
503
- <p className="text-sm text-text-muted mt-2">Successfully transferred</p>
504
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
+ />
505
529
  </div>
506
530
 
507
- {/* Stripe Connect */}
508
- <div className="bg-surface-elevated border border-border rounded-2xl p-6">
509
- <div className="flex items-center justify-between">
510
- <div>
511
- <h3 className="font-semibold text-lg mb-1">Payout Settings</h3>
512
- <p className="text-sm text-text-muted">
513
- Connect your Stripe account to receive payouts
514
- </p>
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>
515
566
  </div>
516
- {earnings?.stripeOnboardingComplete ? (
517
- <div className="flex items-center gap-2 text-green-500">
518
- <Check className="w-5 h-5" />
519
- <span className="font-medium">Connected</span>
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
+ )}
520
588
  </div>
521
- ) : (
522
- <button className="btn-primary !py-2 !px-4" disabled>
523
- <CreditCard className="w-4 h-4" />
524
- Coming Soon
525
- </button>
526
- )}
589
+ </div>
527
590
  </div>
528
- </div>
591
+ )}
529
592
 
530
- {/* Payout History */}
531
- {hasEarnings && earnings.payouts.length > 0 && (
532
- <div className="bg-surface-elevated border border-border rounded-2xl overflow-hidden">
533
- <div className="p-6 border-b border-border">
534
- <h3 className="font-semibold text-lg">Payout History</h3>
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
+ ))}
535
609
  </div>
536
- <div className="divide-y divide-border">
537
- {earnings.payouts.map((payout) => (
538
- <div key={payout.id} className="p-4 flex items-center justify-between">
539
- <div className="flex items-center gap-4">
540
- <div className={`w-10 h-10 rounded-full flex items-center justify-center ${
541
- payout.status === "completed"
542
- ? "bg-green-500/20"
543
- : payout.status === "processing"
544
- ? "bg-yellow-500/20"
545
- : "bg-blue-500/20"
546
- }`}>
547
- {payout.status === "completed" ? (
548
- <Check className="w-5 h-5 text-green-500" />
549
- ) : payout.status === "processing" ? (
550
- <RefreshCw className="w-5 h-5 text-yellow-600 animate-spin" />
551
- ) : (
552
- <Clock className="w-5 h-5 text-blue-500" />
553
- )}
554
- </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" />
555
622
  <div>
556
- <p className="font-medium">${payout.amount.toFixed(2)}</p>
557
- <p className="text-sm text-text-muted">
558
- {new Date(payout.periodStart).toLocaleDateString()} -{" "}
559
- {new Date(payout.periodEnd).toLocaleDateString()}
560
- </p>
623
+ <p className="font-medium">{api.name}</p>
624
+ <p className="text-sm text-text-muted">{api.category}</p>
561
625
  </div>
562
626
  </div>
563
- <span className={`px-2 py-1 rounded-full text-xs font-medium capitalize ${
564
- payout.status === "completed"
565
- ? "bg-green-500/20 text-green-500"
566
- : payout.status === "processing"
567
- ? "bg-yellow-500/20 text-yellow-600"
568
- : "bg-blue-500/20 text-blue-500"
569
- }`}>
570
- {payout.status}
571
- </span>
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>
572
633
  </div>
573
634
  ))}
574
635
  </div>
575
- </div>
576
- )}
636
+ ) : (
637
+ <p className="text-text-muted text-center py-8">No APIs listed yet</p>
638
+ )}
639
+ </div>
577
640
 
578
- {!hasEarnings && (
641
+ {totalCalls === 0 && !analytics?.isPreview && (
579
642
  <div className="rounded-2xl border border-dashed border-border bg-surface/50 p-12 text-center">
580
- <DollarSign className="w-12 h-12 text-text-muted mx-auto mb-4" />
581
- <h3 className="font-semibold text-lg mb-2">No Earnings Yet</h3>
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>
582
645
  <p className="text-text-muted">
583
- When agents start using your APIs, earnings will appear here.
646
+ When agents start using your APIs, analytics stats will appear here.
584
647
  </p>
585
648
  </div>
586
649
  )}