@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,418 @@
|
|
|
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
|
+
PlayCircle,
|
|
8
|
+
Loader2,
|
|
9
|
+
AlertCircle,
|
|
10
|
+
ChevronLeft,
|
|
11
|
+
Check,
|
|
12
|
+
X,
|
|
13
|
+
Clock,
|
|
14
|
+
ChevronDown,
|
|
15
|
+
Send,
|
|
16
|
+
} from "lucide-react";
|
|
17
|
+
import { convexQuery, convexMutation } from "@/lib/convex-client";
|
|
18
|
+
|
|
19
|
+
interface ActionParam {
|
|
20
|
+
name: string;
|
|
21
|
+
type: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
description: string;
|
|
24
|
+
default?: unknown;
|
|
25
|
+
in: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ProviderAction {
|
|
29
|
+
_id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
displayName: string;
|
|
32
|
+
description: string;
|
|
33
|
+
method: string;
|
|
34
|
+
path: string;
|
|
35
|
+
params: ActionParam[];
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface DirectCallConfig {
|
|
40
|
+
_id: string;
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
authType: string;
|
|
43
|
+
status: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface TestResult {
|
|
47
|
+
success: boolean;
|
|
48
|
+
status?: number;
|
|
49
|
+
data?: Record<string, unknown> | string | null;
|
|
50
|
+
error?: string;
|
|
51
|
+
latencyMs: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const methodColors: Record<string, string> = {
|
|
55
|
+
GET: "bg-green-500/20 text-green-500",
|
|
56
|
+
POST: "bg-blue-500/20 text-blue-500",
|
|
57
|
+
PUT: "bg-yellow-500/20 text-yellow-600",
|
|
58
|
+
PATCH: "bg-orange-500/20 text-orange-500",
|
|
59
|
+
DELETE: "bg-red-500/20 text-red-500",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default function TestConsolePage() {
|
|
63
|
+
const params = useParams();
|
|
64
|
+
const router = useRouter();
|
|
65
|
+
const apiId = params.apiId as string;
|
|
66
|
+
|
|
67
|
+
const [config, setConfig] = useState<DirectCallConfig | null>(null);
|
|
68
|
+
const [actions, setActions] = useState<ProviderAction[]>([]);
|
|
69
|
+
const [selectedAction, setSelectedAction] = useState<ProviderAction | null>(null);
|
|
70
|
+
const [paramValues, setParamValues] = useState<Record<string, string>>({});
|
|
71
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
72
|
+
const [isTesting, setIsTesting] = useState(false);
|
|
73
|
+
const [error, setError] = useState<string | null>(null);
|
|
74
|
+
const [result, setResult] = useState<TestResult | null>(null);
|
|
75
|
+
const [showActionDropdown, setShowActionDropdown] = useState(false);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const loadData = async () => {
|
|
79
|
+
const token = localStorage.getItem("apiclaw_session");
|
|
80
|
+
if (!token) {
|
|
81
|
+
router.push("/providers/dashboard/login");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Get direct call config
|
|
87
|
+
const configData = await convexQuery<DirectCallConfig | null>(
|
|
88
|
+
"directCall:getConfig",
|
|
89
|
+
{ apiId }
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (!configData) {
|
|
93
|
+
setError("Direct Call not configured. Please set it up first.");
|
|
94
|
+
setIsLoading(false);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
setConfig(configData);
|
|
98
|
+
|
|
99
|
+
// Get actions
|
|
100
|
+
const actionsData = await convexQuery<ProviderAction[]>(
|
|
101
|
+
"directCall:getActions",
|
|
102
|
+
{ directCallId: configData._id }
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const enabledActions = (actionsData || []).filter((a) => a.enabled);
|
|
106
|
+
setActions(enabledActions);
|
|
107
|
+
|
|
108
|
+
if (enabledActions.length > 0) {
|
|
109
|
+
handleActionSelect(enabledActions[0]);
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error("Failed to load test console:", err);
|
|
113
|
+
setError(err instanceof Error ? err.message : "Failed to load data");
|
|
114
|
+
} finally {
|
|
115
|
+
setIsLoading(false);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
loadData();
|
|
120
|
+
}, [apiId, router]);
|
|
121
|
+
|
|
122
|
+
const handleActionSelect = (action: ProviderAction) => {
|
|
123
|
+
setSelectedAction(action);
|
|
124
|
+
setShowActionDropdown(false);
|
|
125
|
+
setResult(null);
|
|
126
|
+
|
|
127
|
+
// Initialize param values with defaults
|
|
128
|
+
const defaults: Record<string, string> = {};
|
|
129
|
+
action.params.forEach((p) => {
|
|
130
|
+
if (p.default !== undefined) {
|
|
131
|
+
defaults[p.name] = String(p.default);
|
|
132
|
+
} else {
|
|
133
|
+
defaults[p.name] = "";
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
setParamValues(defaults);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const handleTest = async () => {
|
|
140
|
+
if (!selectedAction || !config) return;
|
|
141
|
+
|
|
142
|
+
// Validate required params
|
|
143
|
+
for (const param of selectedAction.params) {
|
|
144
|
+
if (param.required && !paramValues[param.name]) {
|
|
145
|
+
setError(`Parameter "${param.name}" is required`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setIsTesting(true);
|
|
151
|
+
setError(null);
|
|
152
|
+
setResult(null);
|
|
153
|
+
|
|
154
|
+
const startTime = Date.now();
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const token = localStorage.getItem("apiclaw_session");
|
|
158
|
+
|
|
159
|
+
const response = await convexMutation<{
|
|
160
|
+
success: boolean;
|
|
161
|
+
status?: number;
|
|
162
|
+
data?: unknown;
|
|
163
|
+
error?: string;
|
|
164
|
+
}>("directCall:testAction", {
|
|
165
|
+
token,
|
|
166
|
+
directCallId: config._id,
|
|
167
|
+
actionId: selectedAction._id,
|
|
168
|
+
params: paramValues,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
setResult({
|
|
172
|
+
success: Boolean(response.success),
|
|
173
|
+
status: response.status as number | undefined,
|
|
174
|
+
data: response.data as Record<string, unknown> | string | null,
|
|
175
|
+
error: response.error as string | undefined,
|
|
176
|
+
latencyMs: Date.now() - startTime,
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
setResult({
|
|
180
|
+
success: false,
|
|
181
|
+
error: err instanceof Error ? err.message : "Test failed",
|
|
182
|
+
latencyMs: Date.now() - startTime,
|
|
183
|
+
});
|
|
184
|
+
} finally {
|
|
185
|
+
setIsTesting(false);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (isLoading) {
|
|
190
|
+
return (
|
|
191
|
+
<div className="flex items-center justify-center py-24">
|
|
192
|
+
<Loader2 className="w-8 h-8 text-accent animate-spin" />
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (error && !config) {
|
|
198
|
+
return (
|
|
199
|
+
<div className="max-w-2xl mx-auto text-center py-24">
|
|
200
|
+
<AlertCircle className="w-16 h-16 text-yellow-500 mx-auto mb-4" />
|
|
201
|
+
<h1 className="text-2xl font-bold mb-2">Test Console Unavailable</h1>
|
|
202
|
+
<p className="text-text-muted mb-6">{error}</p>
|
|
203
|
+
<Link href={`/providers/dashboard/${apiId}/direct-call`} className="btn-primary">
|
|
204
|
+
Configure Direct Call
|
|
205
|
+
</Link>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<div className="max-w-4xl space-y-6">
|
|
212
|
+
{/* Header */}
|
|
213
|
+
<div className="flex items-center gap-4">
|
|
214
|
+
<Link
|
|
215
|
+
href={`/providers/dashboard/${apiId}`}
|
|
216
|
+
className="p-2 rounded-lg hover:bg-surface transition"
|
|
217
|
+
>
|
|
218
|
+
<ChevronLeft className="w-5 h-5" />
|
|
219
|
+
</Link>
|
|
220
|
+
<div>
|
|
221
|
+
<h1 className="text-2xl font-bold">Test Console</h1>
|
|
222
|
+
<p className="text-text-muted">Test your API actions before going live</p>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
{/* Status Banner */}
|
|
227
|
+
{config?.status === "draft" && (
|
|
228
|
+
<div className="p-4 rounded-xl bg-yellow-500/10 border border-yellow-500/30 flex items-center gap-3">
|
|
229
|
+
<AlertCircle className="w-5 h-5 text-yellow-500 flex-shrink-0" />
|
|
230
|
+
<p className="text-sm text-yellow-600">
|
|
231
|
+
Your Direct Call is in draft mode. Set it to Testing or Live to enable actual API calls.
|
|
232
|
+
</p>
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
{actions.length === 0 ? (
|
|
237
|
+
<div className="text-center py-16 rounded-2xl border border-dashed border-border">
|
|
238
|
+
<PlayCircle className="w-12 h-12 text-text-muted mx-auto mb-4" />
|
|
239
|
+
<h2 className="text-xl font-semibold mb-2">No Actions Defined</h2>
|
|
240
|
+
<p className="text-text-muted mb-6">Create some actions to test them here.</p>
|
|
241
|
+
<Link href={`/providers/dashboard/${apiId}/actions`} className="btn-primary">
|
|
242
|
+
Define Actions
|
|
243
|
+
</Link>
|
|
244
|
+
</div>
|
|
245
|
+
) : (
|
|
246
|
+
<div className="grid lg:grid-cols-2 gap-6">
|
|
247
|
+
{/* Left: Action Selection & Params */}
|
|
248
|
+
<div className="space-y-6">
|
|
249
|
+
{/* Action Selector */}
|
|
250
|
+
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
251
|
+
<h2 className="font-semibold mb-4">Select Action</h2>
|
|
252
|
+
<div className="relative">
|
|
253
|
+
<button
|
|
254
|
+
onClick={() => setShowActionDropdown(!showActionDropdown)}
|
|
255
|
+
className="w-full px-4 py-3 rounded-xl bg-surface border border-border hover:border-accent/50 transition flex items-center justify-between"
|
|
256
|
+
>
|
|
257
|
+
{selectedAction ? (
|
|
258
|
+
<div className="flex items-center gap-3">
|
|
259
|
+
<span className={`px-2 py-1 rounded text-xs font-bold ${methodColors[selectedAction.method] || "bg-gray-500/20"}`}>
|
|
260
|
+
{selectedAction.method}
|
|
261
|
+
</span>
|
|
262
|
+
<span className="font-medium">{selectedAction.displayName}</span>
|
|
263
|
+
</div>
|
|
264
|
+
) : (
|
|
265
|
+
<span className="text-text-muted">Select an action...</span>
|
|
266
|
+
)}
|
|
267
|
+
<ChevronDown className={`w-5 h-5 text-text-muted transition ${showActionDropdown ? "rotate-180" : ""}`} />
|
|
268
|
+
</button>
|
|
269
|
+
|
|
270
|
+
{showActionDropdown && (
|
|
271
|
+
<div className="absolute z-10 w-full mt-2 py-2 rounded-xl bg-surface-elevated border border-border shadow-xl max-h-64 overflow-auto">
|
|
272
|
+
{actions.map((action) => (
|
|
273
|
+
<button
|
|
274
|
+
key={action._id}
|
|
275
|
+
onClick={() => handleActionSelect(action)}
|
|
276
|
+
className={`w-full px-4 py-3 text-left hover:bg-surface transition flex items-center gap-3 ${
|
|
277
|
+
selectedAction?._id === action._id ? "bg-accent/10" : ""
|
|
278
|
+
}`}
|
|
279
|
+
>
|
|
280
|
+
<span className={`px-2 py-1 rounded text-xs font-bold ${methodColors[action.method] || "bg-gray-500/20"}`}>
|
|
281
|
+
{action.method}
|
|
282
|
+
</span>
|
|
283
|
+
<div>
|
|
284
|
+
<p className="font-medium">{action.displayName}</p>
|
|
285
|
+
<p className="text-xs text-text-muted">{action.path}</p>
|
|
286
|
+
</div>
|
|
287
|
+
</button>
|
|
288
|
+
))}
|
|
289
|
+
</div>
|
|
290
|
+
)}
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
{selectedAction && (
|
|
294
|
+
<p className="mt-3 text-sm text-text-muted">{selectedAction.description}</p>
|
|
295
|
+
)}
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
{/* Parameters */}
|
|
299
|
+
{selectedAction && selectedAction.params.length > 0 && (
|
|
300
|
+
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
301
|
+
<h2 className="font-semibold mb-4">Parameters</h2>
|
|
302
|
+
<div className="space-y-4">
|
|
303
|
+
{selectedAction.params.map((param) => (
|
|
304
|
+
<div key={param.name}>
|
|
305
|
+
<label className="block text-sm font-medium mb-1">
|
|
306
|
+
{param.name}
|
|
307
|
+
{param.required && <span className="text-red-500 ml-1">*</span>}
|
|
308
|
+
<span className="text-text-muted font-normal ml-2">({param.type})</span>
|
|
309
|
+
</label>
|
|
310
|
+
<input
|
|
311
|
+
type={param.type === "number" ? "number" : "text"}
|
|
312
|
+
value={paramValues[param.name] || ""}
|
|
313
|
+
onChange={(e) =>
|
|
314
|
+
setParamValues((prev) => ({
|
|
315
|
+
...prev,
|
|
316
|
+
[param.name]: e.target.value,
|
|
317
|
+
}))
|
|
318
|
+
}
|
|
319
|
+
placeholder={param.description}
|
|
320
|
+
className="w-full px-4 py-2 rounded-lg bg-surface border border-border focus:border-accent focus:outline-none transition"
|
|
321
|
+
/>
|
|
322
|
+
{param.description && (
|
|
323
|
+
<p className="mt-1 text-xs text-text-muted">{param.description}</p>
|
|
324
|
+
)}
|
|
325
|
+
</div>
|
|
326
|
+
))}
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
)}
|
|
330
|
+
|
|
331
|
+
{/* Test Button */}
|
|
332
|
+
<button
|
|
333
|
+
onClick={handleTest}
|
|
334
|
+
disabled={!selectedAction || isTesting}
|
|
335
|
+
className="w-full btn-primary disabled:opacity-50 disabled:cursor-not-allowed"
|
|
336
|
+
>
|
|
337
|
+
{isTesting ? (
|
|
338
|
+
<>
|
|
339
|
+
<Loader2 className="w-5 h-5 animate-spin" />
|
|
340
|
+
Testing...
|
|
341
|
+
</>
|
|
342
|
+
) : (
|
|
343
|
+
<>
|
|
344
|
+
<Send className="w-5 h-5" />
|
|
345
|
+
Run Test
|
|
346
|
+
</>
|
|
347
|
+
)}
|
|
348
|
+
</button>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
{/* Right: Results */}
|
|
352
|
+
<div className="rounded-2xl border border-border bg-surface-elevated p-6">
|
|
353
|
+
<h2 className="font-semibold mb-4">Response</h2>
|
|
354
|
+
|
|
355
|
+
{error && (
|
|
356
|
+
<div className="p-4 rounded-xl bg-red-500/10 border border-red-500/30 flex items-center gap-3 text-red-500 mb-4">
|
|
357
|
+
<AlertCircle className="w-5 h-5 flex-shrink-0" />
|
|
358
|
+
<span className="text-sm">{error}</span>
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
|
|
362
|
+
{!result && !error && (
|
|
363
|
+
<div className="flex flex-col items-center justify-center py-12 text-text-muted">
|
|
364
|
+
<PlayCircle className="w-12 h-12 mb-4 opacity-50" />
|
|
365
|
+
<p>Run a test to see results</p>
|
|
366
|
+
</div>
|
|
367
|
+
)}
|
|
368
|
+
|
|
369
|
+
{result && (
|
|
370
|
+
<div className="space-y-4">
|
|
371
|
+
{/* Status */}
|
|
372
|
+
<div className="flex items-center justify-between p-3 rounded-lg bg-surface">
|
|
373
|
+
<div className="flex items-center gap-2">
|
|
374
|
+
{result.success ? (
|
|
375
|
+
<Check className="w-5 h-5 text-green-500" />
|
|
376
|
+
) : (
|
|
377
|
+
<X className="w-5 h-5 text-red-500" />
|
|
378
|
+
)}
|
|
379
|
+
<span className={result.success ? "text-green-500" : "text-red-500"}>
|
|
380
|
+
{result.success ? "Success" : "Failed"}
|
|
381
|
+
</span>
|
|
382
|
+
{result.status && (
|
|
383
|
+
<span className="px-2 py-0.5 rounded bg-surface-elevated text-xs">
|
|
384
|
+
HTTP {result.status}
|
|
385
|
+
</span>
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
<div className="flex items-center gap-1 text-sm text-text-muted">
|
|
389
|
+
<Clock className="w-4 h-4" />
|
|
390
|
+
{result.latencyMs}ms
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
{result.error && (
|
|
395
|
+
<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/30">
|
|
396
|
+
<p className="text-sm text-red-500">{String(result.error)}</p>
|
|
397
|
+
</div>
|
|
398
|
+
)}
|
|
399
|
+
|
|
400
|
+
{/* Response Data */}
|
|
401
|
+
{result.data && (
|
|
402
|
+
<div>
|
|
403
|
+
<h3 className="text-sm font-medium mb-2">Response Body</h3>
|
|
404
|
+
<pre className="p-4 rounded-lg bg-surface text-sm overflow-x-auto font-mono max-h-80 overflow-y-auto">
|
|
405
|
+
{typeof result.data === "string"
|
|
406
|
+
? String(result.data)
|
|
407
|
+
: JSON.stringify(result.data, null, 2)}
|
|
408
|
+
</pre>
|
|
409
|
+
</div>
|
|
410
|
+
)}
|
|
411
|
+
</div>
|
|
412
|
+
)}
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
</div>
|
|
417
|
+
);
|
|
418
|
+
}
|