@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.
- package/AGENTS.md +50 -33
- package/README.md +22 -12
- package/SOUL.md +60 -19
- package/STATUS.md +91 -169
- package/convex/_generated/api.d.ts +6 -0
- package/convex/directCall.ts +598 -0
- package/convex/providers.ts +341 -26
- package/convex/schema.ts +87 -0
- package/convex/usage.ts +260 -0
- package/convex/waitlist.ts +55 -0
- package/data/combined-02-26.json +22102 -0
- package/data/night-expansion-02-26-06-batch2.json +1898 -0
- package/data/night-expansion-02-26-06-batch3.json +1410 -0
- package/data/night-expansion-02-26-06.json +3146 -0
- package/data/night-expansion-02-26-full.json +9726 -0
- package/data/night-expansion-02-26-v2.json +330 -0
- package/data/night-expansion-02-26.json +171 -0
- package/dist/crypto.d.ts +7 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +67 -0
- package/dist/crypto.js.map +1 -0
- package/dist/execute-dynamic.d.ts +116 -0
- package/dist/execute-dynamic.d.ts.map +1 -0
- package/dist/execute-dynamic.js +456 -0
- package/dist/execute-dynamic.js.map +1 -0
- package/dist/execute.d.ts +2 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +35 -5
- package/dist/execute.js.map +1 -1
- package/dist/index.js +33 -4
- package/dist/index.js.map +1 -1
- package/dist/registry/apis.json +2081 -3
- package/docs/PRD-customer-key-passthrough.md +184 -0
- package/landing/public/badges/available-on-apiclaw.svg +14 -0
- package/landing/scripts/generate-stats.js +75 -4
- package/landing/src/app/admin/page.tsx +1 -1
- package/landing/src/app/api/auth/magic-link/route.ts +1 -1
- package/landing/src/app/api/auth/session/route.ts +1 -1
- package/landing/src/app/api/auth/verify/route.ts +1 -1
- package/landing/src/app/api/og/route.tsx +5 -3
- package/landing/src/app/docs/page.tsx +5 -4
- package/landing/src/app/earn/page.tsx +14 -11
- package/landing/src/app/globals.css +16 -15
- package/landing/src/app/layout.tsx +2 -2
- package/landing/src/app/page.tsx +425 -254
- package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +600 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +583 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +301 -0
- package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +659 -0
- package/landing/src/app/providers/dashboard/[apiId]/page.tsx +381 -0
- package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +418 -0
- package/landing/src/app/providers/dashboard/layout.tsx +292 -0
- package/landing/src/app/providers/dashboard/page.tsx +353 -290
- package/landing/src/app/providers/register/page.tsx +87 -10
- package/landing/src/components/AiClientDropdown.tsx +85 -0
- package/landing/src/components/ConfigHelperModal.tsx +113 -0
- package/landing/src/components/HeroTabs.tsx +187 -0
- package/landing/src/components/ShareIntegrationModal.tsx +198 -0
- package/landing/src/hooks/useDashboardData.ts +53 -1
- package/landing/src/lib/apis.json +46554 -174
- package/landing/src/lib/convex-client.ts +22 -3
- package/landing/src/lib/stats.json +4 -4
- package/landing/tsconfig.tsbuildinfo +1 -1
- package/night-expansion-02-26-06-batch2.py +368 -0
- package/night-expansion-02-26-06-batch3.py +299 -0
- package/night-expansion-02-26-06.py +756 -0
- package/package.json +1 -1
- package/scripts/bulk-add-public-apis-v2.py +418 -0
- package/scripts/night-expansion-02-26-v2.py +296 -0
- package/scripts/night-expansion-02-26.py +890 -0
- package/scripts/seed-complete-api.js +181 -0
- package/scripts/seed-demo-api.sh +44 -0
- package/src/crypto.ts +75 -0
- package/src/execute-dynamic.ts +589 -0
- package/src/execute.ts +41 -5
- package/src/index.ts +38 -4
- package/src/registry/apis.json +2081 -3
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { useParams, useRouter } from "next/navigation";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import {
|
|
7
|
+
Zap,
|
|
8
|
+
PlayCircle,
|
|
9
|
+
Settings,
|
|
10
|
+
RefreshCw,
|
|
11
|
+
ExternalLink,
|
|
12
|
+
TrendingUp,
|
|
13
|
+
Users,
|
|
14
|
+
Clock,
|
|
15
|
+
Check,
|
|
16
|
+
AlertCircle,
|
|
17
|
+
Loader2,
|
|
18
|
+
ArrowRight,
|
|
19
|
+
BarChart3,
|
|
20
|
+
Globe,
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
import { convexQuery, type ProviderAPI } from "@/lib/convex-client";
|
|
23
|
+
|
|
24
|
+
interface DirectCallConfig {
|
|
25
|
+
_id: string;
|
|
26
|
+
status: "draft" | "testing" | "live";
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
authType?: string;
|
|
29
|
+
rateLimitPerUser?: number;
|
|
30
|
+
pricePerRequest?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default function ApiOverviewPage() {
|
|
34
|
+
const params = useParams();
|
|
35
|
+
const router = useRouter();
|
|
36
|
+
const apiId = params.apiId as string;
|
|
37
|
+
|
|
38
|
+
const [api, setApi] = useState<ProviderAPI | null>(null);
|
|
39
|
+
const [directCallConfig, setDirectCallConfig] = useState<DirectCallConfig | null>(null);
|
|
40
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
41
|
+
const [error, setError] = useState<string | null>(null);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const loadData = async () => {
|
|
45
|
+
const token = localStorage.getItem("apiclaw_session");
|
|
46
|
+
if (!token) {
|
|
47
|
+
router.push("/providers/dashboard/login");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Load API details
|
|
53
|
+
const apiData = await convexQuery<ProviderAPI | null>("providers:getApiById", { apiId });
|
|
54
|
+
if (!apiData) {
|
|
55
|
+
setError("API not found");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setApi(apiData);
|
|
59
|
+
|
|
60
|
+
// Load Direct Call config if exists
|
|
61
|
+
try {
|
|
62
|
+
const dcConfig = await convexQuery<DirectCallConfig | null>("directCall:getConfig", { apiId });
|
|
63
|
+
setDirectCallConfig(dcConfig);
|
|
64
|
+
} catch {
|
|
65
|
+
// Direct Call not configured yet - that's fine
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error("Failed to load API:", err);
|
|
69
|
+
setError(err instanceof Error ? err.message : "Failed to load API");
|
|
70
|
+
} finally {
|
|
71
|
+
setIsLoading(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
loadData();
|
|
76
|
+
}, [apiId, router]);
|
|
77
|
+
|
|
78
|
+
if (isLoading) {
|
|
79
|
+
return (
|
|
80
|
+
<div className="flex items-center justify-center py-24">
|
|
81
|
+
<Loader2 className="w-8 h-8 text-accent animate-spin" />
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (error || !api) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="text-center py-24">
|
|
89
|
+
<AlertCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
|
90
|
+
<h1 className="text-2xl font-bold mb-2">API Not Found</h1>
|
|
91
|
+
<p className="text-text-muted mb-6">{error || "The requested API could not be found."}</p>
|
|
92
|
+
<Link href="/providers/dashboard" className="btn-primary">
|
|
93
|
+
Back to Dashboard
|
|
94
|
+
</Link>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const getStatusBadge = (status: string) => {
|
|
100
|
+
switch (status) {
|
|
101
|
+
case "live":
|
|
102
|
+
return (
|
|
103
|
+
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium bg-green-500/20 text-green-500">
|
|
104
|
+
<span className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
|
|
105
|
+
Live
|
|
106
|
+
</span>
|
|
107
|
+
);
|
|
108
|
+
case "testing":
|
|
109
|
+
return (
|
|
110
|
+
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium bg-yellow-500/20 text-yellow-600">
|
|
111
|
+
<span className="w-2 h-2 rounded-full bg-yellow-500" />
|
|
112
|
+
Testing
|
|
113
|
+
</span>
|
|
114
|
+
);
|
|
115
|
+
case "draft":
|
|
116
|
+
default:
|
|
117
|
+
return (
|
|
118
|
+
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium bg-gray-500/20 text-gray-500">
|
|
119
|
+
<span className="w-2 h-2 rounded-full bg-gray-500" />
|
|
120
|
+
Draft
|
|
121
|
+
</span>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div className="space-y-8">
|
|
128
|
+
{/* Header */}
|
|
129
|
+
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
130
|
+
<div className="flex items-center gap-4">
|
|
131
|
+
<div className="w-14 h-14 rounded-2xl bg-accent/20 flex items-center justify-center text-3xl">
|
|
132
|
+
🔌
|
|
133
|
+
</div>
|
|
134
|
+
<div>
|
|
135
|
+
<h1 className="text-2xl font-bold">{api.name}</h1>
|
|
136
|
+
<div className="flex items-center gap-3 text-sm text-text-muted">
|
|
137
|
+
<span>{api.category}</span>
|
|
138
|
+
<span>•</span>
|
|
139
|
+
<span className={`capitalize ${
|
|
140
|
+
api.status === "approved" ? "text-green-500" : "text-yellow-600"
|
|
141
|
+
}`}>
|
|
142
|
+
{api.status}
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
{api.docsUrl && (
|
|
148
|
+
<a
|
|
149
|
+
href={api.docsUrl}
|
|
150
|
+
target="_blank"
|
|
151
|
+
rel="noopener noreferrer"
|
|
152
|
+
className="btn-secondary !py-2 !px-4 text-sm"
|
|
153
|
+
>
|
|
154
|
+
<ExternalLink className="w-4 h-4" />
|
|
155
|
+
View Docs
|
|
156
|
+
</a>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
{/* Quick Stats */}
|
|
161
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
162
|
+
<div className="rounded-xl border border-border bg-surface-elevated p-4">
|
|
163
|
+
<div className="flex items-center gap-2 mb-2">
|
|
164
|
+
<Users className="w-4 h-4 text-text-muted" />
|
|
165
|
+
<span className="text-sm text-text-muted">Discoveries</span>
|
|
166
|
+
</div>
|
|
167
|
+
<p className="text-2xl font-bold">{api.discoveryCount || 0}</p>
|
|
168
|
+
</div>
|
|
169
|
+
<div className="rounded-xl border border-border bg-surface-elevated p-4">
|
|
170
|
+
<div className="flex items-center gap-2 mb-2">
|
|
171
|
+
<TrendingUp className="w-4 h-4 text-text-muted" />
|
|
172
|
+
<span className="text-sm text-text-muted">Pricing</span>
|
|
173
|
+
</div>
|
|
174
|
+
<p className="text-xl font-bold capitalize">{api.pricingModel}</p>
|
|
175
|
+
</div>
|
|
176
|
+
<div className="rounded-xl border border-border bg-surface-elevated p-4">
|
|
177
|
+
<div className="flex items-center gap-2 mb-2">
|
|
178
|
+
<Clock className="w-4 h-4 text-text-muted" />
|
|
179
|
+
<span className="text-sm text-text-muted">Listed</span>
|
|
180
|
+
</div>
|
|
181
|
+
<p className="text-lg font-medium">
|
|
182
|
+
{new Date(api.createdAt).toLocaleDateString()}
|
|
183
|
+
</p>
|
|
184
|
+
</div>
|
|
185
|
+
<div className="rounded-xl border border-border bg-surface-elevated p-4">
|
|
186
|
+
<div className="flex items-center gap-2 mb-2">
|
|
187
|
+
<PlayCircle className="w-4 h-4 text-text-muted" />
|
|
188
|
+
<span className="text-sm text-text-muted">Direct Call</span>
|
|
189
|
+
</div>
|
|
190
|
+
{directCallConfig ? (
|
|
191
|
+
getStatusBadge(directCallConfig.status)
|
|
192
|
+
) : (
|
|
193
|
+
<span className="text-sm text-text-muted">Not configured</span>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* Description */}
|
|
199
|
+
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
200
|
+
<h2 className="font-semibold mb-3">Description</h2>
|
|
201
|
+
<p className="text-text-secondary">{api.description}</p>
|
|
202
|
+
{api.pricingNotes && (
|
|
203
|
+
<div className="mt-4 pt-4 border-t border-border">
|
|
204
|
+
<h3 className="text-sm font-medium text-text-muted mb-2">Pricing Notes</h3>
|
|
205
|
+
<p className="text-text-secondary text-sm">{api.pricingNotes}</p>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{/* Direct Call Setup Card */}
|
|
211
|
+
<div className={`rounded-2xl border p-6 ${
|
|
212
|
+
directCallConfig?.status === "live"
|
|
213
|
+
? "border-green-500/30 bg-green-500/5"
|
|
214
|
+
: directCallConfig
|
|
215
|
+
? "border-yellow-500/30 bg-yellow-500/5"
|
|
216
|
+
: "border-accent/30 bg-accent/5"
|
|
217
|
+
}`}>
|
|
218
|
+
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
219
|
+
<div className="flex items-start gap-4">
|
|
220
|
+
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
|
|
221
|
+
directCallConfig?.status === "live"
|
|
222
|
+
? "bg-green-500/20"
|
|
223
|
+
: directCallConfig
|
|
224
|
+
? "bg-yellow-500/20"
|
|
225
|
+
: "bg-accent/20"
|
|
226
|
+
}`}>
|
|
227
|
+
<PlayCircle className={`w-6 h-6 ${
|
|
228
|
+
directCallConfig?.status === "live"
|
|
229
|
+
? "text-green-500"
|
|
230
|
+
: directCallConfig
|
|
231
|
+
? "text-yellow-600"
|
|
232
|
+
: "text-accent"
|
|
233
|
+
}`} />
|
|
234
|
+
</div>
|
|
235
|
+
<div>
|
|
236
|
+
<h2 className="font-semibold text-lg mb-1">Direct Call Setup</h2>
|
|
237
|
+
{directCallConfig ? (
|
|
238
|
+
<>
|
|
239
|
+
<p className="text-text-secondary text-sm mb-2">
|
|
240
|
+
{directCallConfig.status === "live"
|
|
241
|
+
? "Your API is live and accepting calls through APIClaw."
|
|
242
|
+
: directCallConfig.status === "testing"
|
|
243
|
+
? "Your Direct Call setup is in testing mode."
|
|
244
|
+
: "Complete your Direct Call configuration to go live."
|
|
245
|
+
}
|
|
246
|
+
</p>
|
|
247
|
+
{directCallConfig.baseUrl && (
|
|
248
|
+
<div className="flex items-center gap-2 text-sm text-text-muted">
|
|
249
|
+
<Globe className="w-4 h-4" />
|
|
250
|
+
<span className="font-mono">{directCallConfig.baseUrl}</span>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
</>
|
|
254
|
+
) : (
|
|
255
|
+
<p className="text-text-secondary text-sm">
|
|
256
|
+
Enable Direct Call to let agents use your API directly through APIClaw with unified billing.
|
|
257
|
+
</p>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
<Link
|
|
262
|
+
href={`/providers/dashboard/${apiId}/direct-call`}
|
|
263
|
+
className={`btn-primary !py-2.5 !px-5 whitespace-nowrap ${
|
|
264
|
+
directCallConfig?.status === "live" ? "!bg-green-600 hover:!bg-green-700" : ""
|
|
265
|
+
}`}
|
|
266
|
+
>
|
|
267
|
+
{directCallConfig ? (
|
|
268
|
+
<>
|
|
269
|
+
<Settings className="w-4 h-4" />
|
|
270
|
+
Configure
|
|
271
|
+
</>
|
|
272
|
+
) : (
|
|
273
|
+
<>
|
|
274
|
+
<Zap className="w-4 h-4" />
|
|
275
|
+
Set Up Direct Call
|
|
276
|
+
</>
|
|
277
|
+
)}
|
|
278
|
+
</Link>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{/* Quick config summary */}
|
|
282
|
+
{directCallConfig && (directCallConfig.rateLimitPerUser || directCallConfig.pricePerRequest) && (
|
|
283
|
+
<div className="mt-4 pt-4 border-t border-border/50 grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
284
|
+
<div>
|
|
285
|
+
<p className="text-xs text-text-muted mb-1">Auth Type</p>
|
|
286
|
+
<p className="text-sm font-medium capitalize">{directCallConfig.authType || "Not set"}</p>
|
|
287
|
+
</div>
|
|
288
|
+
<div>
|
|
289
|
+
<p className="text-xs text-text-muted mb-1">Rate Limit/User</p>
|
|
290
|
+
<p className="text-sm font-medium">{directCallConfig.rateLimitPerUser || "Unlimited"}/min</p>
|
|
291
|
+
</div>
|
|
292
|
+
<div>
|
|
293
|
+
<p className="text-xs text-text-muted mb-1">Price/Request</p>
|
|
294
|
+
<p className="text-sm font-medium">{directCallConfig.pricePerRequest || 0} credits</p>
|
|
295
|
+
</div>
|
|
296
|
+
<div>
|
|
297
|
+
<p className="text-xs text-text-muted mb-1">Status</p>
|
|
298
|
+
{getStatusBadge(directCallConfig.status)}
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
{/* Action Cards */}
|
|
305
|
+
<div className="grid md:grid-cols-2 gap-4">
|
|
306
|
+
<Link
|
|
307
|
+
href={`/providers/dashboard/${apiId}/actions`}
|
|
308
|
+
className="group rounded-2xl border border-border bg-surface-elevated p-6 hover:border-accent/50 transition"
|
|
309
|
+
>
|
|
310
|
+
<div className="flex items-center justify-between mb-4">
|
|
311
|
+
<div className="w-12 h-12 rounded-xl bg-surface flex items-center justify-center">
|
|
312
|
+
<Settings className="w-6 h-6 text-text-muted group-hover:text-accent transition" />
|
|
313
|
+
</div>
|
|
314
|
+
<ArrowRight className="w-5 h-5 text-text-muted group-hover:text-accent group-hover:translate-x-1 transition-all" />
|
|
315
|
+
</div>
|
|
316
|
+
<h3 className="font-semibold mb-1">Actions</h3>
|
|
317
|
+
<p className="text-sm text-text-muted">
|
|
318
|
+
Define the endpoints and methods agents can call.
|
|
319
|
+
</p>
|
|
320
|
+
</Link>
|
|
321
|
+
|
|
322
|
+
<Link
|
|
323
|
+
href={`/providers/dashboard/${apiId}/test`}
|
|
324
|
+
className="group rounded-2xl border border-border bg-surface-elevated p-6 hover:border-accent/50 transition"
|
|
325
|
+
>
|
|
326
|
+
<div className="flex items-center justify-between mb-4">
|
|
327
|
+
<div className="w-12 h-12 rounded-xl bg-surface flex items-center justify-center">
|
|
328
|
+
<RefreshCw className="w-6 h-6 text-text-muted group-hover:text-accent transition" />
|
|
329
|
+
</div>
|
|
330
|
+
<ArrowRight className="w-5 h-5 text-text-muted group-hover:text-accent group-hover:translate-x-1 transition-all" />
|
|
331
|
+
</div>
|
|
332
|
+
<h3 className="font-semibold mb-1">Test Console</h3>
|
|
333
|
+
<p className="text-sm text-text-muted">
|
|
334
|
+
Test your API actions before going live.
|
|
335
|
+
</p>
|
|
336
|
+
</Link>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
{/* Technical Details */}
|
|
340
|
+
{(api.openApiUrl || api.docsUrl) && (
|
|
341
|
+
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
342
|
+
<h2 className="font-semibold mb-4">Technical Details</h2>
|
|
343
|
+
<div className="space-y-3">
|
|
344
|
+
{api.openApiUrl && (
|
|
345
|
+
<div className="flex items-center justify-between p-3 rounded-lg bg-surface">
|
|
346
|
+
<div className="flex items-center gap-3">
|
|
347
|
+
<BarChart3 className="w-5 h-5 text-text-muted" />
|
|
348
|
+
<span className="text-sm">OpenAPI Spec</span>
|
|
349
|
+
</div>
|
|
350
|
+
<a
|
|
351
|
+
href={api.openApiUrl}
|
|
352
|
+
target="_blank"
|
|
353
|
+
rel="noopener noreferrer"
|
|
354
|
+
className="text-accent hover:underline text-sm flex items-center gap-1"
|
|
355
|
+
>
|
|
356
|
+
View <ExternalLink className="w-3 h-3" />
|
|
357
|
+
</a>
|
|
358
|
+
</div>
|
|
359
|
+
)}
|
|
360
|
+
{api.docsUrl && (
|
|
361
|
+
<div className="flex items-center justify-between p-3 rounded-lg bg-surface">
|
|
362
|
+
<div className="flex items-center gap-3">
|
|
363
|
+
<ExternalLink className="w-5 h-5 text-text-muted" />
|
|
364
|
+
<span className="text-sm">Documentation</span>
|
|
365
|
+
</div>
|
|
366
|
+
<a
|
|
367
|
+
href={api.docsUrl}
|
|
368
|
+
target="_blank"
|
|
369
|
+
rel="noopener noreferrer"
|
|
370
|
+
className="text-accent hover:underline text-sm flex items-center gap-1"
|
|
371
|
+
>
|
|
372
|
+
View <ExternalLink className="w-3 h-3" />
|
|
373
|
+
</a>
|
|
374
|
+
</div>
|
|
375
|
+
)}
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
)}
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|