@nordsym/apiclaw 1.0.0 → 1.1.1

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 (161) hide show
  1. package/AGENTS.md +74 -0
  2. package/HEARTBEAT.md +4 -0
  3. package/IDENTITY.md +22 -0
  4. package/README.md +193 -202
  5. package/SOUL.md +36 -0
  6. package/STATUS.md +237 -0
  7. package/TOOLS.md +36 -0
  8. package/USER.md +17 -0
  9. package/{backend/convex → convex}/_generated/api.d.ts +12 -6
  10. package/convex/analytics.ts +90 -0
  11. package/convex/credits.ts +211 -0
  12. package/convex/http.ts +578 -0
  13. package/convex/providers.ts +516 -0
  14. package/convex/purchases.ts +183 -0
  15. package/convex/ratelimit.ts +104 -0
  16. package/convex/schema.ts +220 -0
  17. package/convex/telemetry.ts +81 -0
  18. package/convex.json +3 -0
  19. package/dist/credentials.d.ts +19 -0
  20. package/dist/credentials.d.ts.map +1 -0
  21. package/dist/credentials.js +158 -0
  22. package/dist/credentials.js.map +1 -0
  23. package/dist/credits.d.ts +14 -11
  24. package/dist/credits.d.ts.map +1 -1
  25. package/dist/credits.js +151 -99
  26. package/dist/credits.js.map +1 -1
  27. package/dist/discovery.d.ts +7 -16
  28. package/dist/discovery.d.ts.map +1 -1
  29. package/dist/discovery.js +33 -40
  30. package/dist/discovery.js.map +1 -1
  31. package/dist/execute.d.ts +19 -0
  32. package/dist/execute.d.ts.map +1 -0
  33. package/dist/execute.js +285 -0
  34. package/dist/execute.js.map +1 -0
  35. package/dist/index.js +175 -31
  36. package/dist/index.js.map +1 -1
  37. package/dist/proxy.d.ts +6 -0
  38. package/dist/proxy.d.ts.map +1 -0
  39. package/dist/proxy.js +19 -0
  40. package/dist/proxy.js.map +1 -0
  41. package/dist/registry/apis.json +95362 -202
  42. package/dist/registry/apis_expanded.json +100853 -0
  43. package/dist/stripe.d.ts +68 -0
  44. package/dist/stripe.d.ts.map +1 -0
  45. package/dist/stripe.js +196 -0
  46. package/dist/stripe.js.map +1 -0
  47. package/dist/telemetry.d.ts +28 -0
  48. package/dist/telemetry.d.ts.map +1 -0
  49. package/dist/telemetry.js +50 -0
  50. package/dist/telemetry.js.map +1 -0
  51. package/dist/test.d.ts +3 -2
  52. package/dist/test.d.ts.map +1 -1
  53. package/dist/test.js +105 -75
  54. package/dist/test.js.map +1 -1
  55. package/dist/types.d.ts +0 -28
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/webhook.d.ts +2 -0
  58. package/dist/webhook.d.ts.map +1 -0
  59. package/dist/webhook.js +90 -0
  60. package/dist/webhook.js.map +1 -0
  61. package/landing/DESIGN.md +343 -0
  62. package/landing/package-lock.json +1196 -7
  63. package/landing/package.json +5 -1
  64. package/landing/public/android-chrome-192x192.png +0 -0
  65. package/landing/public/android-chrome-512x512.png +0 -0
  66. package/landing/public/apple-touch-icon.png +0 -0
  67. package/landing/public/demo.gif +0 -0
  68. package/landing/public/demo.mp4 +0 -0
  69. package/landing/public/favicon-16x16.png +0 -0
  70. package/landing/public/favicon-32x32.png +0 -0
  71. package/landing/public/favicon.ico +0 -0
  72. package/landing/public/favicon.svg +3 -0
  73. package/landing/public/icon.svg +47 -0
  74. package/landing/public/logo-mono.svg +37 -0
  75. package/landing/public/logo-simple.svg +45 -0
  76. package/landing/public/logo.svg +84 -0
  77. package/landing/public/og-template.html +184 -0
  78. package/landing/public/site.webmanifest +31 -0
  79. package/landing/scripts/generate-assets.js +284 -0
  80. package/landing/scripts/generate-pngs.js +48 -0
  81. package/landing/scripts/generate-stats.js +42 -0
  82. package/landing/src/app/admin/page.tsx +348 -0
  83. package/landing/src/app/api/auth/magic-link/route.ts +73 -0
  84. package/landing/src/app/api/auth/session/route.ts +38 -0
  85. package/landing/src/app/api/auth/verify/route.ts +43 -0
  86. package/landing/src/app/api/og/route.tsx +84 -0
  87. package/landing/src/app/globals.css +439 -100
  88. package/landing/src/app/layout.tsx +37 -7
  89. package/landing/src/app/page.tsx +627 -552
  90. package/landing/src/app/providers/dashboard/login/page.tsx +176 -0
  91. package/landing/src/app/providers/dashboard/page.tsx +589 -0
  92. package/landing/src/app/providers/dashboard/verify/page.tsx +106 -0
  93. package/landing/src/app/providers/layout.tsx +14 -0
  94. package/landing/src/app/providers/page.tsx +402 -0
  95. package/landing/src/app/providers/register/page.tsx +670 -0
  96. package/landing/src/components/ProviderDashboard.tsx +794 -0
  97. package/landing/src/hooks/useDashboardData.ts +99 -0
  98. package/landing/src/lib/apis.json +116054 -0
  99. package/landing/src/lib/convex-client.ts +106 -0
  100. package/landing/src/lib/mock-data.ts +285 -0
  101. package/landing/src/lib/stats.json +6 -0
  102. package/landing/tailwind.config.ts +12 -11
  103. package/landing/tsconfig.tsbuildinfo +1 -0
  104. package/package.json +21 -20
  105. package/scripts/SYMBOT-FIX.md +238 -0
  106. package/scripts/demo-simulation.py +177 -0
  107. package/scripts/expand-more.py +502 -0
  108. package/scripts/expand-registry.py +434 -0
  109. package/scripts/history-sanitizer.ts +272 -0
  110. package/scripts/mass-scrape.py +1308 -0
  111. package/scripts/sync-and-deploy.sh +36 -0
  112. package/src/credentials.ts +177 -0
  113. package/src/credits.ts +190 -122
  114. package/src/discovery.ts +45 -58
  115. package/src/execute.ts +350 -0
  116. package/src/index.ts +184 -32
  117. package/src/proxy.ts +24 -0
  118. package/src/registry/apis.json +95362 -202
  119. package/src/registry/apis_expanded.json +100853 -0
  120. package/src/stripe.ts +243 -0
  121. package/src/telemetry.ts +71 -0
  122. package/src/test.ts +127 -89
  123. package/src/types.ts +0 -34
  124. package/src/webhook.ts +107 -0
  125. package/.github/ISSUE_TEMPLATE/add-api.yml +0 -123
  126. package/BRIEFING.md +0 -30
  127. package/backend/convex/apiKeys.ts +0 -75
  128. package/backend/convex/purchases.ts +0 -74
  129. package/backend/convex/schema.ts +0 -45
  130. package/backend/convex/transactions.ts +0 -57
  131. package/backend/convex/users.ts +0 -94
  132. package/backend/package-lock.json +0 -521
  133. package/backend/package.json +0 -15
  134. package/dist/registry/parse_apis.py +0 -146
  135. package/dist/revenuecat.d.ts +0 -61
  136. package/dist/revenuecat.d.ts.map +0 -1
  137. package/dist/revenuecat.js +0 -166
  138. package/dist/revenuecat.js.map +0 -1
  139. package/dist/webhooks/revenuecat.d.ts +0 -48
  140. package/dist/webhooks/revenuecat.d.ts.map +0 -1
  141. package/dist/webhooks/revenuecat.js +0 -119
  142. package/dist/webhooks/revenuecat.js.map +0 -1
  143. package/docs/revenuecat-setup.md +0 -89
  144. package/landing/src/app/api/keys/route.ts +0 -71
  145. package/landing/src/app/api/log/route.ts +0 -37
  146. package/landing/src/app/api/stats/route.ts +0 -37
  147. package/landing/src/app/page.tsx.bak +0 -567
  148. package/landing/src/components/AddKeyModal.tsx +0 -159
  149. package/newsletter-template.html +0 -71
  150. package/outreach/OUTREACH-SYSTEM.md +0 -211
  151. package/outreach/email-template.html +0 -179
  152. package/outreach/targets.md +0 -133
  153. package/src/registry/parse_apis.py +0 -146
  154. package/src/revenuecat.ts +0 -239
  155. package/src/webhooks/revenuecat.ts +0 -187
  156. /package/{backend/convex → convex}/README.md +0 -0
  157. /package/{backend/convex → convex}/_generated/api.js +0 -0
  158. /package/{backend/convex → convex}/_generated/dataModel.d.ts +0 -0
  159. /package/{backend/convex → convex}/_generated/server.d.ts +0 -0
  160. /package/{backend/convex → convex}/_generated/server.js +0 -0
  161. /package/{backend/convex → convex}/tsconfig.json +0 -0
@@ -0,0 +1,589 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import {
5
+ BarChart3,
6
+ CreditCard,
7
+ Settings,
8
+ TrendingUp,
9
+ Users,
10
+ Zap,
11
+ DollarSign,
12
+ ArrowUpRight,
13
+ ArrowDownRight,
14
+ ExternalLink,
15
+ ChevronRight,
16
+ Check,
17
+ Clock,
18
+ AlertCircle,
19
+ LogOut,
20
+ Loader2,
21
+ RefreshCw,
22
+ Plus,
23
+ } from "lucide-react";
24
+ import {
25
+ LineChart,
26
+ Line,
27
+ BarChart,
28
+ Bar,
29
+ XAxis,
30
+ YAxis,
31
+ CartesianGrid,
32
+ Tooltip,
33
+ ResponsiveContainer,
34
+ PieChart,
35
+ Pie,
36
+ Cell,
37
+ } from "recharts";
38
+ import Link from "next/link";
39
+ import { useDashboardData } from "@/hooks/useDashboardData";
40
+ import type { ProviderAPI, Analytics, Earnings } from "@/lib/convex-client";
41
+
42
+ type TabType = "overview" | "apis" | "earnings";
43
+
44
+ const COLORS = ["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6"];
45
+
46
+ export default function DashboardPage() {
47
+ const { session, apis, analytics, earnings, isLoading, error, refresh, logout } = useDashboardData();
48
+ const [activeTab, setActiveTab] = useState<TabType>("overview");
49
+
50
+ const tabs = [
51
+ { id: "overview" as TabType, label: "Overview", icon: BarChart3 },
52
+ { id: "apis" as TabType, label: "APIs", icon: Zap },
53
+ { id: "earnings" as TabType, label: "Earnings", icon: CreditCard },
54
+ ];
55
+
56
+ if (isLoading) {
57
+ return (
58
+ <div className="min-h-screen flex items-center justify-center">
59
+ <div className="text-center">
60
+ <Loader2 className="w-12 h-12 text-accent animate-spin mx-auto mb-4" />
61
+ <p className="text-text-muted">Loading dashboard...</p>
62
+ </div>
63
+ </div>
64
+ );
65
+ }
66
+
67
+ if (error) {
68
+ return (
69
+ <div className="min-h-screen flex items-center justify-center px-6">
70
+ <div className="text-center max-w-md">
71
+ <AlertCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
72
+ <h1 className="text-2xl font-bold mb-2">Something Went Wrong</h1>
73
+ <p className="text-text-muted mb-6">{error}</p>
74
+ <button onClick={refresh} className="btn-primary">
75
+ <RefreshCw className="w-5 h-5" />
76
+ Try Again
77
+ </button>
78
+ </div>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ if (!session) {
84
+ return null; // Will redirect to login via hook
85
+ }
86
+
87
+ return (
88
+ <div className="min-h-screen bg-background">
89
+ {/* Header */}
90
+ <header className="border-b border-border bg-surface/50 backdrop-blur-xl sticky top-0 z-50">
91
+ <div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
92
+ <div className="flex items-center gap-4">
93
+ <Link href="/" className="w-10 h-10 rounded-xl bg-accent/20 flex items-center justify-center text-xl">
94
+ 🦞
95
+ </Link>
96
+ <div>
97
+ <h1 className="font-bold text-lg">Provider Dashboard</h1>
98
+ <p className="text-sm text-text-muted">{session.name || session.email}</p>
99
+ </div>
100
+ </div>
101
+ <div className="flex items-center gap-3">
102
+ <button
103
+ onClick={refresh}
104
+ className="p-2 rounded-lg hover:bg-surface transition"
105
+ title="Refresh"
106
+ >
107
+ <RefreshCw className="w-5 h-5 text-text-muted" />
108
+ </button>
109
+ <button
110
+ onClick={logout}
111
+ className="p-2 rounded-lg hover:bg-surface transition"
112
+ title="Sign out"
113
+ >
114
+ <LogOut className="w-5 h-5 text-text-muted" />
115
+ </button>
116
+ </div>
117
+ </div>
118
+ </header>
119
+
120
+ <div className="max-w-7xl mx-auto px-6 py-8">
121
+ {/* Tab Navigation */}
122
+ <div className="flex items-center gap-1 p-1 bg-surface rounded-xl w-fit mb-8">
123
+ {tabs.map((tab) => (
124
+ <button
125
+ key={tab.id}
126
+ onClick={() => setActiveTab(tab.id)}
127
+ className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${
128
+ activeTab === tab.id
129
+ ? "bg-accent text-white"
130
+ : "text-text-muted hover:text-text-primary"
131
+ }`}
132
+ >
133
+ <tab.icon className="w-4 h-4" />
134
+ {tab.label}
135
+ </button>
136
+ ))}
137
+ </div>
138
+
139
+ {/* Tab Content */}
140
+ {activeTab === "overview" && (
141
+ <OverviewTab apis={apis} analytics={analytics} />
142
+ )}
143
+ {activeTab === "apis" && <ApisTab apis={apis} />}
144
+ {activeTab === "earnings" && <EarningsTab earnings={earnings} />}
145
+ </div>
146
+ </div>
147
+ );
148
+ }
149
+
150
+ // ============================================
151
+ // OVERVIEW TAB
152
+ // ============================================
153
+
154
+ function OverviewTab({
155
+ apis,
156
+ analytics,
157
+ }: {
158
+ apis: ProviderAPI[];
159
+ analytics: Analytics | null;
160
+ }) {
161
+ const hasData = analytics && analytics.totalCalls > 0;
162
+
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
+ </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>
186
+ </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>
193
+ </div>
194
+ </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>
216
+ </div>
217
+
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>
232
+ <span className={`px-2 py-0.5 rounded-full text-xs font-medium ${
233
+ api.status === "approved" ? "bg-green-500/20 text-green-500" : "bg-yellow-500/20 text-yellow-600"
234
+ }`}>
235
+ {api.status}
236
+ </span>
237
+ </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
+ </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>
257
+ </div>
258
+ )}
259
+ </div>
260
+ </div>
261
+ </div>
262
+ );
263
+ }
264
+
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>
329
+ </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
+ )}
352
+ </div>
353
+ </div>
354
+ </div>
355
+ )}
356
+ </div>
357
+ );
358
+ }
359
+
360
+ function StatCard({
361
+ title,
362
+ value,
363
+ change,
364
+ icon: Icon,
365
+ accent,
366
+ }: {
367
+ title: string;
368
+ value: string;
369
+ change?: number;
370
+ icon: typeof Zap;
371
+ accent?: boolean;
372
+ }) {
373
+ return (
374
+ <div className={`rounded-2xl border p-5 ${accent ? "bg-accent/10 border-accent/30" : "bg-surface-elevated border-border"}`}>
375
+ <div className="flex items-center justify-between mb-3">
376
+ <span className="text-sm text-text-muted">{title}</span>
377
+ <Icon className={`w-5 h-5 ${accent ? "text-accent" : "text-text-muted"}`} />
378
+ </div>
379
+ <div className="flex items-end justify-between">
380
+ <span className={`text-3xl font-bold ${accent ? "text-accent" : ""}`}>{value}</span>
381
+ {change !== undefined && (
382
+ <div className={`flex items-center gap-1 text-sm ${change >= 0 ? "text-green-500" : "text-red-500"}`}>
383
+ {change >= 0 ? <ArrowUpRight className="w-4 h-4" /> : <ArrowDownRight className="w-4 h-4" />}
384
+ {Math.abs(change).toFixed(1)}%
385
+ </div>
386
+ )}
387
+ </div>
388
+ </div>
389
+ );
390
+ }
391
+
392
+ // ============================================
393
+ // APIS TAB
394
+ // ============================================
395
+
396
+ function ApisTab({ apis }: { apis: ProviderAPI[] }) {
397
+ return (
398
+ <div className="space-y-6">
399
+ <div className="flex items-center justify-between">
400
+ <h2 className="text-2xl font-bold">Your APIs</h2>
401
+ <Link href="/providers/register" className="btn-primary !py-2 !px-4 text-sm">
402
+ <Plus className="w-4 h-4" />
403
+ Add API
404
+ </Link>
405
+ </div>
406
+
407
+ {apis.length === 0 ? (
408
+ <div className="text-center py-16 rounded-2xl border border-dashed border-border bg-surface/50">
409
+ <Zap className="w-12 h-12 text-text-muted mx-auto mb-4" />
410
+ <h3 className="font-semibold text-lg mb-2">No APIs Listed</h3>
411
+ <p className="text-text-muted mb-6">List your first API to make it discoverable by AI agents.</p>
412
+ <Link href="/providers/register" className="btn-primary">
413
+ <Plus className="w-5 h-5" />
414
+ List Your First API
415
+ </Link>
416
+ </div>
417
+ ) : (
418
+ <div className="grid gap-4">
419
+ {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>
427
+ </div>
428
+ </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>
440
+ <div className="flex items-center gap-6 text-sm">
441
+ <div>
442
+ <span className="text-text-muted">Pricing:</span>{" "}
443
+ <span className="capitalize">{api.pricingModel}</span>
444
+ </div>
445
+ <div>
446
+ <span className="text-text-muted">Discoveries:</span>{" "}
447
+ <span>{api.discoveryCount || 0}</span>
448
+ </div>
449
+ {api.docsUrl && (
450
+ <a
451
+ href={api.docsUrl}
452
+ target="_blank"
453
+ rel="noopener noreferrer"
454
+ className="text-accent hover:underline flex items-center gap-1"
455
+ >
456
+ Documentation <ExternalLink className="w-3 h-3" />
457
+ </a>
458
+ )}
459
+ </div>
460
+ </div>
461
+ ))}
462
+ </div>
463
+ )}
464
+ </div>
465
+ );
466
+ }
467
+
468
+ // ============================================
469
+ // EARNINGS TAB
470
+ // ============================================
471
+
472
+ function EarningsTab({ earnings }: { earnings: Earnings | null }) {
473
+ const hasEarnings = earnings && (earnings.totalEarned > 0 || earnings.payouts.length > 0);
474
+
475
+ return (
476
+ <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>
501
+ </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
+ </div>
505
+ </div>
506
+
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>
515
+ </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>
520
+ </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
+ )}
527
+ </div>
528
+ </div>
529
+
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>
535
+ </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>
555
+ <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>
561
+ </div>
562
+ </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>
572
+ </div>
573
+ ))}
574
+ </div>
575
+ </div>
576
+ )}
577
+
578
+ {!hasEarnings && (
579
+ <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>
582
+ <p className="text-text-muted">
583
+ When agents start using your APIs, earnings will appear here.
584
+ </p>
585
+ </div>
586
+ )}
587
+ </div>
588
+ );
589
+ }