@djangocfg/ui-tools 2.1.268 → 2.1.270

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 (40) hide show
  1. package/dist/PlaygroundLayout-FRKIMYVN.mjs +684 -0
  2. package/dist/PlaygroundLayout-FRKIMYVN.mjs.map +1 -0
  3. package/dist/PlaygroundLayout-LIAN63CZ.cjs +691 -0
  4. package/dist/PlaygroundLayout-LIAN63CZ.cjs.map +1 -0
  5. package/dist/{PrettyCode.client-OO3KAJSM.mjs → PrettyCode.client-DW5LTG47.mjs} +5 -5
  6. package/dist/PrettyCode.client-DW5LTG47.mjs.map +1 -0
  7. package/dist/{PrettyCode.client-V2ZN5DTH.cjs → PrettyCode.client-SGDGQTYT.cjs} +5 -5
  8. package/dist/PrettyCode.client-SGDGQTYT.cjs.map +1 -0
  9. package/dist/{chunk-SZ2CZEQZ.mjs → chunk-FX3GCEUL.mjs} +5 -26
  10. package/dist/chunk-FX3GCEUL.mjs.map +1 -0
  11. package/dist/{chunk-CRHHUOVJ.cjs → chunk-VAL2LCQD.cjs} +4 -27
  12. package/dist/chunk-VAL2LCQD.cjs.map +1 -0
  13. package/dist/index.cjs +8 -8
  14. package/dist/index.mjs +5 -5
  15. package/package.json +6 -6
  16. package/src/tools/OpenapiViewer/README.md +121 -0
  17. package/src/tools/OpenapiViewer/components/PlaygroundLayout/EndpointList.tsx +221 -0
  18. package/src/tools/OpenapiViewer/components/PlaygroundLayout/RequestPanel.tsx +231 -0
  19. package/src/tools/OpenapiViewer/components/PlaygroundLayout/ResponsePanel.tsx +112 -0
  20. package/src/tools/OpenapiViewer/components/PlaygroundLayout/index.tsx +107 -0
  21. package/src/tools/OpenapiViewer/components/PlaygroundLayout/ui.tsx +137 -0
  22. package/src/tools/OpenapiViewer/components/index.ts +0 -9
  23. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +1 -1
  24. package/src/tools/PrettyCode/PrettyCode.client.tsx +17 -12
  25. package/dist/PlaygroundLayout-FKXSULJ3.cjs +0 -971
  26. package/dist/PlaygroundLayout-FKXSULJ3.cjs.map +0 -1
  27. package/dist/PlaygroundLayout-XMMHPZYP.mjs +0 -964
  28. package/dist/PlaygroundLayout-XMMHPZYP.mjs.map +0 -1
  29. package/dist/PrettyCode.client-OO3KAJSM.mjs.map +0 -1
  30. package/dist/PrettyCode.client-V2ZN5DTH.cjs.map +0 -1
  31. package/dist/chunk-CRHHUOVJ.cjs.map +0 -1
  32. package/dist/chunk-SZ2CZEQZ.mjs.map +0 -1
  33. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +0 -149
  34. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +0 -278
  35. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +0 -91
  36. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +0 -100
  37. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +0 -157
  38. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +0 -253
  39. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +0 -173
  40. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +0 -68
@@ -0,0 +1,684 @@
1
+ import { usePlaygroundContext, deduplicateEndpoints, isValidJson, findApiKeyById, parseRequestHeaders, PrettyCode_default } from './chunk-FX3GCEUL.mjs';
2
+ import { JsonTree_default } from './chunk-LFWQ36LJ.mjs';
3
+ import './chunk-SSUOENAZ.mjs';
4
+ import { __name } from './chunk-CGILA3WO.mjs';
5
+ import React4, { useState, useMemo, useEffect, useCallback } from 'react';
6
+ import { cn } from '@djangocfg/ui-core/lib';
7
+ import { useIsMobile } from '@djangocfg/ui-core/hooks';
8
+ import { ChevronRight, Search, Filter, Send, Key, Terminal, Loader2 } from 'lucide-react';
9
+ import { Skeleton, Combobox, Input, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DownloadButton, Textarea, CopyButton, Button } from '@djangocfg/ui-core/components';
10
+ import consola from 'consola';
11
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
+
13
+ var useMobile = /* @__PURE__ */ __name(() => {
14
+ const isMobile = useIsMobile();
15
+ return {
16
+ isMobile,
17
+ isDesktop: !isMobile
18
+ };
19
+ }, "useMobile");
20
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
21
+ var extractEndpoints = /* @__PURE__ */ __name((schema) => {
22
+ const endpoints = [];
23
+ if (!schema.paths) return [];
24
+ const baseUrl = schema.servers && schema.servers.length > 0 ? schema.servers[0].url : "";
25
+ for (const [path, methods] of Object.entries(schema.paths)) {
26
+ for (const method of HTTP_METHODS) {
27
+ const op = methods[method];
28
+ if (!op) continue;
29
+ const methodUpper = method.toUpperCase();
30
+ const description = op.description || op.summary || `${methodUpper} ${path}`;
31
+ const category = op.tags?.[0] || "Other";
32
+ const parameters = [];
33
+ const allParams = [...methods.parameters || [], ...op.parameters || []];
34
+ for (const param of allParams) {
35
+ parameters.push({
36
+ name: param.name,
37
+ type: param.schema?.type || "string",
38
+ required: param.required || false,
39
+ description: param.description
40
+ });
41
+ }
42
+ const responses = [];
43
+ if (op.responses) {
44
+ for (const [code, response] of Object.entries(op.responses)) {
45
+ responses.push({
46
+ code,
47
+ description: response.description || `Response ${code}`
48
+ });
49
+ }
50
+ }
51
+ let requestBody;
52
+ if (op.requestBody) {
53
+ const content = op.requestBody.content;
54
+ const mediaType = content?.["application/json"] || content?.[Object.keys(content || {})[0]];
55
+ requestBody = {
56
+ type: mediaType?.schema?.type || "object",
57
+ description: op.requestBody.description
58
+ };
59
+ }
60
+ const endpoint = {
61
+ name: path.split("/").pop() || path,
62
+ method: methodUpper,
63
+ path: baseUrl + path,
64
+ description,
65
+ category,
66
+ parameters: parameters.length > 0 ? parameters : void 0,
67
+ requestBody,
68
+ responses: responses.length > 0 ? responses : void 0
69
+ };
70
+ endpoints.push(endpoint);
71
+ }
72
+ }
73
+ return endpoints;
74
+ }, "extractEndpoints");
75
+ var getCategories = /* @__PURE__ */ __name((endpoints) => {
76
+ const categories = /* @__PURE__ */ new Set();
77
+ endpoints.forEach((endpoint) => categories.add(endpoint.category));
78
+ return Array.from(categories).sort();
79
+ }, "getCategories");
80
+ var fetchSchema = /* @__PURE__ */ __name(async (url) => {
81
+ const response = await fetch(url, {
82
+ headers: {
83
+ "Accept": "application/json"
84
+ }
85
+ });
86
+ if (!response.ok) {
87
+ throw new Error(`Failed to fetch schema: ${response.statusText}`);
88
+ }
89
+ return response.json();
90
+ }, "fetchSchema");
91
+ function useOpenApiSchema({
92
+ schemas,
93
+ defaultSchemaId
94
+ }) {
95
+ const [loading, setLoading] = useState(true);
96
+ const [error, setError] = useState(null);
97
+ const [currentSchemaId, setCurrentSchemaId] = useState(
98
+ defaultSchemaId || schemas[0]?.id
99
+ );
100
+ const [loadedSchemas, setLoadedSchemas] = useState(
101
+ /* @__PURE__ */ new Map()
102
+ );
103
+ const currentSchema = useMemo(
104
+ () => schemas.find((s) => s.id === currentSchemaId) || null,
105
+ [schemas, currentSchemaId]
106
+ );
107
+ const currentOpenApiSchema = useMemo(
108
+ () => currentSchemaId ? loadedSchemas.get(currentSchemaId) : null,
109
+ [loadedSchemas, currentSchemaId]
110
+ );
111
+ const endpoints = useMemo(
112
+ () => currentOpenApiSchema ? extractEndpoints(currentOpenApiSchema) : [],
113
+ [currentOpenApiSchema]
114
+ );
115
+ const categories = useMemo(() => getCategories(endpoints), [endpoints]);
116
+ useEffect(() => {
117
+ if (!currentSchema) return;
118
+ if (loadedSchemas.has(currentSchema.id)) {
119
+ setLoading(false);
120
+ return;
121
+ }
122
+ setLoading(true);
123
+ setError(null);
124
+ fetchSchema(currentSchema.url).then((schema) => {
125
+ setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
126
+ consola.success(`Schema loaded: ${currentSchema.name}`);
127
+ setLoading(false);
128
+ }).catch((err) => {
129
+ consola.error(`Error loading schema from ${currentSchema.url}:`, err);
130
+ setError(err instanceof Error ? err.message : "Failed to load schema");
131
+ setLoading(false);
132
+ });
133
+ }, [currentSchema, loadedSchemas]);
134
+ const setCurrentSchema = useCallback((schemaId) => {
135
+ setCurrentSchemaId(schemaId);
136
+ }, []);
137
+ const refresh = useCallback(() => {
138
+ if (!currentSchema) return;
139
+ setLoading(true);
140
+ setError(null);
141
+ setLoadedSchemas((prev) => {
142
+ const next = new Map(prev);
143
+ next.delete(currentSchema.id);
144
+ return next;
145
+ });
146
+ fetchSchema(currentSchema.url).then((schema) => {
147
+ setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
148
+ consola.success(`Schema refreshed: ${currentSchema.name}`);
149
+ setLoading(false);
150
+ }).catch((err) => {
151
+ consola.error(`Error refreshing schema from ${currentSchema.url}:`, err);
152
+ setError(err instanceof Error ? err.message : "Failed to refresh schema");
153
+ setLoading(false);
154
+ });
155
+ }, [currentSchema]);
156
+ return {
157
+ loading,
158
+ error,
159
+ endpoints,
160
+ categories,
161
+ schemas,
162
+ currentSchema,
163
+ setCurrentSchema,
164
+ refresh
165
+ };
166
+ }
167
+ __name(useOpenApiSchema, "useOpenApiSchema");
168
+ var METHOD_STYLES = {
169
+ GET: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25",
170
+ POST: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25",
171
+ PUT: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25",
172
+ PATCH: "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/25",
173
+ DELETE: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25"
174
+ };
175
+ var METHOD_FALLBACK = "bg-muted text-muted-foreground border-border";
176
+ function getMethodStyle(method) {
177
+ return METHOD_STYLES[method.toUpperCase()] ?? METHOD_FALLBACK;
178
+ }
179
+ __name(getMethodStyle, "getMethodStyle");
180
+ function getStatusStyle(status) {
181
+ if (status >= 500) return "bg-red-500/10 text-red-500 dark:text-red-400 border-red-500/25";
182
+ if (status >= 400) return "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25";
183
+ if (status >= 300) return "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25";
184
+ return "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
185
+ }
186
+ __name(getStatusStyle, "getStatusStyle");
187
+ function relativePath(full) {
188
+ try {
189
+ return new URL(full).pathname;
190
+ } catch {
191
+ return full;
192
+ }
193
+ }
194
+ __name(relativePath, "relativePath");
195
+ function MethodBadge({ method }) {
196
+ return /* @__PURE__ */ jsx("span", { className: cn(
197
+ "inline-flex shrink-0 items-center rounded border px-1.5 py-px",
198
+ "font-mono text-[10px] font-bold uppercase tracking-wider leading-none",
199
+ getMethodStyle(method)
200
+ ), children: method });
201
+ }
202
+ __name(MethodBadge, "MethodBadge");
203
+ function StatusBadge({ status }) {
204
+ return /* @__PURE__ */ jsx("span", { className: cn(
205
+ "inline-flex items-center rounded border px-1.5 py-px",
206
+ "font-mono text-[11px] font-bold leading-none",
207
+ getStatusStyle(status)
208
+ ), children: status });
209
+ }
210
+ __name(StatusBadge, "StatusBadge");
211
+ function SectionLabel({ children }) {
212
+ return /* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 select-none", children });
213
+ }
214
+ __name(SectionLabel, "SectionLabel");
215
+ function Panel({ children, className }) {
216
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col min-h-0 overflow-hidden", className), children });
217
+ }
218
+ __name(Panel, "Panel");
219
+ function ScrollArea({ children, className }) {
220
+ return /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-y-auto min-h-0", className), children });
221
+ }
222
+ __name(ScrollArea, "ScrollArea");
223
+ function PanelHeader({ title }) {
224
+ return /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 h-10 flex items-center", children: /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-widest text-muted-foreground/50", children: title }) });
225
+ }
226
+ __name(PanelHeader, "PanelHeader");
227
+ function EmptyState({ icon: Icon, text }) {
228
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-3 px-6 text-center", children: [
229
+ /* @__PURE__ */ jsx(Icon, { className: "h-7 w-7 text-muted-foreground/25" }),
230
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: text })
231
+ ] });
232
+ }
233
+ __name(EmptyState, "EmptyState");
234
+ function CollapsibleSection({
235
+ label,
236
+ action,
237
+ children,
238
+ defaultOpen = false
239
+ }) {
240
+ const [open, setOpen] = React4.useState(defaultOpen);
241
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
242
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
243
+ /* @__PURE__ */ jsxs(
244
+ "button",
245
+ {
246
+ type: "button",
247
+ onClick: () => setOpen((v) => !v),
248
+ className: "flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 hover:text-muted-foreground transition-colors py-1",
249
+ children: [
250
+ /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3 w-3 transition-transform", open && "rotate-90") }),
251
+ label
252
+ ]
253
+ }
254
+ ),
255
+ action && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: action })
256
+ ] }),
257
+ open && /* @__PURE__ */ jsx("div", { children })
258
+ ] });
259
+ }
260
+ __name(CollapsibleSection, "CollapsibleSection");
261
+ function EndpointRow({
262
+ method,
263
+ path,
264
+ description,
265
+ isActive,
266
+ onClick
267
+ }) {
268
+ const displayPath = relativePath(path);
269
+ const rowCls = cn(
270
+ "group w-full text-left flex items-start gap-2.5 px-3 py-2.5 transition-colors hover:bg-muted/40",
271
+ isActive && "bg-primary/[0.06] hover:bg-primary/[0.09]"
272
+ );
273
+ const arrowCls = cn(
274
+ "h-3.5 w-3.5 shrink-0 mt-px transition-opacity",
275
+ isActive ? "text-primary opacity-100" : "opacity-0 group-hover:opacity-30"
276
+ );
277
+ return /* @__PURE__ */ jsxs("button", { className: rowCls, onClick, children: [
278
+ /* @__PURE__ */ jsx(MethodBadge, { method }),
279
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
280
+ /* @__PURE__ */ jsx("p", { className: "font-mono text-[11px] text-foreground/75 truncate leading-tight", children: displayPath }),
281
+ description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/60 truncate leading-tight mt-0.5", children: description })
282
+ ] }),
283
+ /* @__PURE__ */ jsx(ChevronRight, { className: arrowCls })
284
+ ] });
285
+ }
286
+ __name(EndpointRow, "EndpointRow");
287
+ function EndpointList() {
288
+ const { state, config, setSelectedEndpoint, setSelectedCategory, setSearchTerm } = usePlaygroundContext();
289
+ const { endpoints, categories, loading, error, schemas, currentSchema, setCurrentSchema } = useOpenApiSchema({ schemas: config.schemas, defaultSchemaId: config.defaultSchemaId });
290
+ const schemaOptions = useMemo(
291
+ () => schemas.map((s) => ({ value: s.id, label: s.name })),
292
+ [schemas]
293
+ );
294
+ const filtered = useMemo(() => {
295
+ let list = deduplicateEndpoints(endpoints, state.selectedVersion);
296
+ if (state.selectedCategory !== "All") {
297
+ list = list.filter((e) => e.category === state.selectedCategory);
298
+ }
299
+ if (state.searchTerm) {
300
+ const q = state.searchTerm.toLowerCase();
301
+ list = list.filter(
302
+ (e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
303
+ );
304
+ }
305
+ return list;
306
+ }, [endpoints, state.selectedCategory, state.searchTerm, state.selectedVersion]);
307
+ const isFiltered = state.selectedCategory !== "All";
308
+ const hasCategories = categories.length > 0;
309
+ const hasMultipleSchemas = schemas.length > 1;
310
+ const endpointLabel = `${filtered.length} endpoint${filtered.length !== 1 ? "s" : ""}`;
311
+ const downloadFilename = currentSchema ? `${currentSchema.id}-openapi.json` : "openapi.json";
312
+ if (loading) {
313
+ return /* @__PURE__ */ jsx("div", { className: "p-3 space-y-1.5", children: Array.from({ length: 12 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full rounded" }, i)) });
314
+ }
315
+ if (error) {
316
+ return /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-destructive", children: [
317
+ "Failed to load schema: ",
318
+ error
319
+ ] }) });
320
+ }
321
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
322
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-2.5 py-2 space-y-2", children: [
323
+ hasMultipleSchemas && /* @__PURE__ */ jsx(
324
+ Combobox,
325
+ {
326
+ options: schemaOptions,
327
+ value: currentSchema?.id ?? "",
328
+ onValueChange: (id) => id && setCurrentSchema(id),
329
+ placeholder: "Select API",
330
+ searchPlaceholder: "Search APIs\u2026",
331
+ emptyText: "No APIs found",
332
+ className: "w-full h-8 text-xs"
333
+ }
334
+ ),
335
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
336
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1 min-w-0", children: [
337
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" }),
338
+ /* @__PURE__ */ jsx(
339
+ Input,
340
+ {
341
+ placeholder: "Search endpoints\u2026",
342
+ value: state.searchTerm,
343
+ onChange: (e) => setSearchTerm(e.target.value),
344
+ className: "pl-8 h-8 text-xs"
345
+ }
346
+ )
347
+ ] }),
348
+ hasCategories && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
349
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("button", { className: cn(
350
+ "relative shrink-0 flex items-center justify-center h-8 w-8 rounded-md border transition-colors",
351
+ isFiltered ? "border-primary bg-primary/10 text-primary" : "border-input bg-background text-muted-foreground hover:text-foreground hover:bg-muted/50"
352
+ ), children: [
353
+ /* @__PURE__ */ jsx(Filter, { className: "h-3.5 w-3.5" }),
354
+ isFiltered && /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 h-2 w-2 rounded-full bg-primary" })
355
+ ] }) }),
356
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", className: "min-w-[160px] max-h-72 overflow-y-auto", children: ["All", ...categories].map((c) => /* @__PURE__ */ jsx(
357
+ DropdownMenuItem,
358
+ {
359
+ onClick: () => setSelectedCategory(c),
360
+ className: cn("text-xs", state.selectedCategory === c && "bg-accent font-medium"),
361
+ children: c
362
+ },
363
+ c
364
+ )) })
365
+ ] })
366
+ ] })
367
+ ] }),
368
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center justify-between px-3 py-1 border-b bg-muted/20", children: [
369
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums", children: endpointLabel }),
370
+ currentSchema && /* @__PURE__ */ jsx(
371
+ DownloadButton,
372
+ {
373
+ url: currentSchema.url,
374
+ filename: downloadFilename,
375
+ variant: "ghost",
376
+ size: "sm",
377
+ className: "h-6 px-2 text-[10px] text-muted-foreground/50 hover:text-foreground",
378
+ children: "JSON"
379
+ }
380
+ )
381
+ ] }),
382
+ /* @__PURE__ */ jsx(ScrollArea, { children: filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "No endpoints found" }) : /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/40", children: filtered.map((ep) => /* @__PURE__ */ jsx(
383
+ EndpointRow,
384
+ {
385
+ method: ep.method,
386
+ path: ep.path,
387
+ description: ep.description,
388
+ isActive: state.selectedEndpoint?.path === ep.path && state.selectedEndpoint?.method === ep.method,
389
+ onClick: () => setSelectedEndpoint(ep)
390
+ },
391
+ `${ep.method}-${ep.path}`
392
+ )) }) })
393
+ ] });
394
+ }
395
+ __name(EndpointList, "EndpointList");
396
+ function ParamFields({ label, params }) {
397
+ const { state, setParameters } = usePlaygroundContext();
398
+ function handleChange(name, value) {
399
+ setParameters({ ...state.parameters, [name]: value });
400
+ }
401
+ __name(handleChange, "handleChange");
402
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
403
+ /* @__PURE__ */ jsx(SectionLabel, { children: label }),
404
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: params.map((p) => {
405
+ const value = state.parameters[p.name] ?? "";
406
+ const placeholder = p.description || p.name;
407
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
408
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
409
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: p.name }),
410
+ p.required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
411
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: p.type })
412
+ ] }),
413
+ /* @__PURE__ */ jsx(
414
+ Input,
415
+ {
416
+ value,
417
+ onChange: (e) => handleChange(p.name, e.target.value),
418
+ placeholder,
419
+ className: "h-8 text-xs font-mono"
420
+ }
421
+ )
422
+ ] }, p.name);
423
+ }) })
424
+ ] });
425
+ }
426
+ __name(ParamFields, "ParamFields");
427
+ function RequestPanel() {
428
+ const { state, apiKeys, setRequestBody, setRequestHeaders, setManualApiToken, sendRequest } = usePlaygroundContext();
429
+ const ep = state.selectedEndpoint;
430
+ const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
431
+ const curlCommand = useMemo(() => {
432
+ if (!state.requestUrl) return "";
433
+ const apiKey = state.selectedApiKey ? findApiKeyById(apiKeys, state.selectedApiKey) : null;
434
+ const hdrs = parseRequestHeaders(state.requestHeaders);
435
+ if (apiKey) hdrs["X-API-Key"] = apiKey.id;
436
+ let cmd = `curl -X ${state.requestMethod} "${state.requestUrl}"`;
437
+ Object.entries(hdrs).forEach(([k, v]) => {
438
+ cmd += ` \\
439
+ -H "${k}: ${v}"`;
440
+ });
441
+ if (state.requestBody && state.requestMethod !== "GET" && isJsonValid) {
442
+ cmd += ` \\
443
+ -d '${state.requestBody}'`;
444
+ }
445
+ return cmd;
446
+ }, [state, apiKeys, isJsonValid]);
447
+ const pathParams = useMemo(
448
+ () => ep?.parameters?.filter((p) => ep.path.includes(`{${p.name}}`)) ?? [],
449
+ [ep]
450
+ );
451
+ const queryParams = useMemo(
452
+ () => ep?.parameters?.filter((p) => !ep.path.includes(`{${p.name}}`)) ?? [],
453
+ [ep]
454
+ );
455
+ const isSendDisabled = state.loading || !state.requestUrl || !isJsonValid;
456
+ const displayUrl = state.requestUrl || ep?.path || "";
457
+ const hasBody = ep?.method !== "GET";
458
+ const bodyType = ep?.requestBody?.type ?? "";
459
+ const hasPathParams = pathParams.length > 0;
460
+ const hasQueryParams = queryParams.length > 0;
461
+ const hasCurl = Boolean(curlCommand);
462
+ const epPath = ep ? relativePath(ep.path) : "";
463
+ const urlChanged = displayUrl !== epPath;
464
+ if (!ep) {
465
+ return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: "Select an endpoint to build a request" });
466
+ }
467
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
468
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-3 bg-muted/20 space-y-1.5", children: [
469
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
470
+ /* @__PURE__ */ jsx(MethodBadge, { method: ep.method }),
471
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-foreground/70 truncate min-w-0", children: epPath })
472
+ ] }),
473
+ urlChanged && /* @__PURE__ */ jsx("div", { className: "font-mono text-[10px] text-muted-foreground/50 break-all leading-snug pl-0.5", children: displayUrl })
474
+ ] }),
475
+ /* @__PURE__ */ jsxs(ScrollArea, { className: "px-4 py-3 space-y-3", children: [
476
+ hasPathParams && /* @__PURE__ */ jsx(ParamFields, { label: "Path Parameters", params: pathParams }),
477
+ hasQueryParams && /* @__PURE__ */ jsx(ParamFields, { label: "Query Parameters", params: queryParams }),
478
+ hasBody && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
479
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
480
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Body" }),
481
+ bodyType && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/40 font-mono", children: bodyType })
482
+ ] }),
483
+ /* @__PURE__ */ jsx(
484
+ Textarea,
485
+ {
486
+ placeholder: '{\n "key": "value"\n}',
487
+ value: state.requestBody,
488
+ onChange: (e) => setRequestBody(e.target.value),
489
+ className: cn(
490
+ "font-mono text-[11px] min-h-[90px] resize-y",
491
+ !isJsonValid && "border-destructive focus-visible:ring-destructive/30"
492
+ ),
493
+ rows: 4
494
+ }
495
+ ),
496
+ !isJsonValid && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-destructive", children: "Invalid JSON" })
497
+ ] }),
498
+ /* @__PURE__ */ jsx(
499
+ CollapsibleSection,
500
+ {
501
+ label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
502
+ /* @__PURE__ */ jsx(Key, { className: "h-2.5 w-2.5" }),
503
+ "Auth & Headers"
504
+ ] }),
505
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 pt-2", children: [
506
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
507
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Bearer Token" }),
508
+ /* @__PURE__ */ jsx(
509
+ Input,
510
+ {
511
+ type: "password",
512
+ placeholder: "Leave empty to use JWT from localStorage",
513
+ value: state.manualApiToken,
514
+ onChange: (e) => setManualApiToken(e.target.value),
515
+ className: "font-mono text-xs h-8"
516
+ }
517
+ )
518
+ ] }),
519
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
520
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Headers" }),
521
+ /* @__PURE__ */ jsx(
522
+ Textarea,
523
+ {
524
+ value: state.requestHeaders,
525
+ onChange: (e) => setRequestHeaders(e.target.value),
526
+ className: "font-mono text-[11px] min-h-[60px] resize-y",
527
+ rows: 3
528
+ }
529
+ )
530
+ ] })
531
+ ] })
532
+ }
533
+ ),
534
+ hasCurl && /* @__PURE__ */ jsx(
535
+ CollapsibleSection,
536
+ {
537
+ label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
538
+ /* @__PURE__ */ jsx(Terminal, { className: "h-2.5 w-2.5" }),
539
+ "cURL"
540
+ ] }),
541
+ action: /* @__PURE__ */ jsx(CopyButton, { value: curlCommand, variant: "ghost", size: "sm", className: "h-5 px-2 text-[10px] text-muted-foreground", children: "Copy" }),
542
+ children: /* @__PURE__ */ jsx("div", { className: "rounded-md overflow-hidden mt-2", children: /* @__PURE__ */ jsx(PrettyCode_default, { data: curlCommand, language: "bash", isCompact: true }) })
543
+ }
544
+ ),
545
+ /* @__PURE__ */ jsx("div", { className: "h-1" })
546
+ ] }),
547
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background/95 backdrop-blur-sm", children: /* @__PURE__ */ jsx(Button, { onClick: sendRequest, disabled: isSendDisabled, size: "sm", className: "w-full gap-2 h-9", children: state.loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
548
+ /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
549
+ " Sending\u2026"
550
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
551
+ /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
552
+ " Send Request"
553
+ ] }) }) })
554
+ ] });
555
+ }
556
+ __name(RequestPanel, "RequestPanel");
557
+ var JSON_TREE_CONFIG = {
558
+ maxAutoExpandDepth: 2,
559
+ maxAutoExpandArrayItems: 10,
560
+ maxAutoExpandObjectKeys: 5,
561
+ maxStringLength: 200,
562
+ collectionLimit: 50,
563
+ showCollectionInfo: true,
564
+ showExpandControls: true,
565
+ showActionButtons: false,
566
+ preserveKeyOrder: true,
567
+ className: "border-0 rounded-none"
568
+ };
569
+ function ResponsePanel() {
570
+ const { state } = usePlaygroundContext();
571
+ const { response, loading, selectedEndpoint } = state;
572
+ const { treeData, rawText } = useMemo(() => {
573
+ const d = response?.data;
574
+ if (d == null) return { treeData: null, rawText: "" };
575
+ if (typeof d === "string") {
576
+ try {
577
+ return { treeData: JSON.parse(d), rawText: d };
578
+ } catch {
579
+ return { treeData: null, rawText: d };
580
+ }
581
+ }
582
+ return { treeData: d, rawText: JSON.stringify(d, null, 2) };
583
+ }, [response?.data]);
584
+ const sizeKb = rawText ? `${(rawText.length / 1024).toFixed(1)} KB` : "";
585
+ const hasError = Boolean(response?.error);
586
+ const hasStatus = response?.status != null;
587
+ const hasCopy = Boolean(rawText);
588
+ if (loading) {
589
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center h-full gap-2", children: [
590
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }),
591
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Sending\u2026" })
592
+ ] });
593
+ }
594
+ if (!selectedEndpoint) return /* @__PURE__ */ jsx(EmptyState, { icon: Terminal, text: "Response will appear here" });
595
+ if (!response) return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: 'Press "Send Request" to see the response' });
596
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
597
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 flex items-center justify-between gap-3 bg-muted/20", children: [
598
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
599
+ hasStatus && /* @__PURE__ */ jsx(StatusBadge, { status: response.status }),
600
+ response.statusText && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: response.statusText }),
601
+ sizeKb && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: sizeKb })
602
+ ] }),
603
+ hasCopy && /* @__PURE__ */ jsx(CopyButton, { value: rawText, variant: "ghost", size: "sm", className: "h-6 px-2 text-[10px] text-muted-foreground shrink-0", children: "Copy" })
604
+ ] }),
605
+ hasError && /* @__PURE__ */ jsx("div", { className: "shrink-0 mx-4 mt-3 rounded border border-destructive/20 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: response.error }) }),
606
+ /* @__PURE__ */ jsx(ScrollArea, { children: treeData != null ? /* @__PURE__ */ jsx(JsonTree_default, { title: "Response Body", data: treeData, config: JSON_TREE_CONFIG }) : rawText ? /* @__PURE__ */ jsx("pre", { className: "p-4 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: rawText }) : /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" }) })
607
+ ] });
608
+ }
609
+ __name(ResponsePanel, "ResponsePanel");
610
+ var MOBILE_TABS = [
611
+ { id: "endpoints", label: "Endpoints" },
612
+ { id: "request", label: "Request" },
613
+ { id: "response", label: "Response" }
614
+ ];
615
+ function MobileView() {
616
+ const { state } = usePlaygroundContext();
617
+ const [tab, setTab] = React4.useState("endpoints");
618
+ React4.useEffect(() => {
619
+ if (state.selectedEndpoint) setTab("request");
620
+ }, [state.selectedEndpoint?.path, state.selectedEndpoint?.method]);
621
+ React4.useEffect(() => {
622
+ if (state.response && !state.loading) setTab("response");
623
+ }, [state.response, state.loading]);
624
+ const hasResponse = Boolean(state.response) && !state.loading;
625
+ return /* @__PURE__ */ jsxs(Panel, { className: "h-full", children: [
626
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 flex border-b", children: MOBILE_TABS.map((t) => {
627
+ const isActive = tab === t.id;
628
+ const showDot = t.id === "response" && hasResponse;
629
+ return /* @__PURE__ */ jsxs(
630
+ "button",
631
+ {
632
+ onClick: () => setTab(t.id),
633
+ className: cn(
634
+ "flex-1 py-2.5 text-xs font-medium transition-colors border-b-2 -mb-px relative",
635
+ isActive ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
636
+ ),
637
+ children: [
638
+ t.label,
639
+ showDot && /* @__PURE__ */ jsx("span", { className: "absolute top-2 right-[calc(50%-16px)] h-1.5 w-1.5 rounded-full bg-primary" })
640
+ ]
641
+ },
642
+ t.id
643
+ );
644
+ }) }),
645
+ /* @__PURE__ */ jsxs(Panel, { className: "flex-1", children: [
646
+ tab === "endpoints" && /* @__PURE__ */ jsx(EndpointList, {}),
647
+ tab === "request" && /* @__PURE__ */ jsx(RequestPanel, {}),
648
+ tab === "response" && /* @__PURE__ */ jsx(ResponsePanel, {})
649
+ ] })
650
+ ] });
651
+ }
652
+ __name(MobileView, "MobileView");
653
+ function DesktopView() {
654
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[260px_1fr_1fr] divide-x h-full min-h-0 overflow-hidden", children: [
655
+ /* @__PURE__ */ jsxs(Panel, { children: [
656
+ /* @__PURE__ */ jsx(PanelHeader, { title: "Endpoints" }),
657
+ /* @__PURE__ */ jsx(EndpointList, {})
658
+ ] }),
659
+ /* @__PURE__ */ jsxs(Panel, { children: [
660
+ /* @__PURE__ */ jsx(PanelHeader, { title: "Request" }),
661
+ /* @__PURE__ */ jsx(RequestPanel, {})
662
+ ] }),
663
+ /* @__PURE__ */ jsxs(Panel, { children: [
664
+ /* @__PURE__ */ jsx(PanelHeader, { title: "Response" }),
665
+ /* @__PURE__ */ jsx(ResponsePanel, {})
666
+ ] })
667
+ ] });
668
+ }
669
+ __name(DesktopView, "DesktopView");
670
+ var PlaygroundLayout = /* @__PURE__ */ __name(() => {
671
+ const { isMobile } = useMobile();
672
+ return /* @__PURE__ */ jsx(
673
+ "div",
674
+ {
675
+ className: "flex flex-col overflow-hidden",
676
+ style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
677
+ children: isMobile ? /* @__PURE__ */ jsx(MobileView, {}) : /* @__PURE__ */ jsx(DesktopView, {})
678
+ }
679
+ );
680
+ }, "PlaygroundLayout");
681
+
682
+ export { PlaygroundLayout };
683
+ //# sourceMappingURL=PlaygroundLayout-FRKIMYVN.mjs.map
684
+ //# sourceMappingURL=PlaygroundLayout-FRKIMYVN.mjs.map