@djangocfg/ui-tools 2.1.268 → 2.1.271

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 (41) hide show
  1. package/dist/PlaygroundLayout-G325I6HM.mjs +736 -0
  2. package/dist/PlaygroundLayout-G325I6HM.mjs.map +1 -0
  3. package/dist/PlaygroundLayout-ZO2LO7M5.cjs +743 -0
  4. package/dist/PlaygroundLayout-ZO2LO7M5.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-QZ55LYK2.mjs} +141 -169
  10. package/dist/chunk-QZ55LYK2.mjs.map +1 -0
  11. package/dist/{chunk-CRHHUOVJ.cjs → chunk-WM4RT5KX.cjs} +139 -169
  12. package/dist/chunk-WM4RT5KX.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 +228 -0
  18. package/src/tools/OpenapiViewer/components/PlaygroundLayout/RequestPanel.tsx +258 -0
  19. package/src/tools/OpenapiViewer/components/PlaygroundLayout/ResponsePanel.tsx +127 -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 +198 -208
  24. package/src/tools/OpenapiViewer/types.ts +1 -0
  25. package/src/tools/PrettyCode/PrettyCode.client.tsx +17 -12
  26. package/dist/PlaygroundLayout-FKXSULJ3.cjs +0 -971
  27. package/dist/PlaygroundLayout-FKXSULJ3.cjs.map +0 -1
  28. package/dist/PlaygroundLayout-XMMHPZYP.mjs +0 -964
  29. package/dist/PlaygroundLayout-XMMHPZYP.mjs.map +0 -1
  30. package/dist/PrettyCode.client-OO3KAJSM.mjs.map +0 -1
  31. package/dist/PrettyCode.client-V2ZN5DTH.cjs.map +0 -1
  32. package/dist/chunk-CRHHUOVJ.cjs.map +0 -1
  33. package/dist/chunk-SZ2CZEQZ.mjs.map +0 -1
  34. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +0 -149
  35. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +0 -278
  36. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +0 -91
  37. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +0 -100
  38. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +0 -157
  39. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +0 -253
  40. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +0 -173
  41. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +0 -68
@@ -1,157 +0,0 @@
1
- 'use client';
2
-
3
- import { Key, Loader2, Send } from 'lucide-react';
4
- import React, { useCallback } from 'react';
5
-
6
- import {
7
- Button, Card, CardContent, CardHeader, CardTitle, CopyButton, Input, Textarea
8
- } from '@djangocfg/ui-core/components';
9
-
10
- import PrettyCode from '../../PrettyCode';
11
- import { usePlaygroundContext } from '../context/PlaygroundContext';
12
- import { findApiKeyById, isValidJson, parseRequestHeaders } from '../utils';
13
- import { EndpointInfo } from './EndpointInfo';
14
- import { RequestParametersForm } from './RequestParametersForm';
15
-
16
- export const RequestBuilder: React.FC = () => {
17
- const {
18
- state,
19
- apiKeys,
20
- setRequestBody,
21
- setManualApiToken,
22
- sendRequest
23
- } = usePlaygroundContext();
24
-
25
- const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
26
-
27
- // Generate cURL command
28
- const generateCurlCommand = () => {
29
- if (!state.requestUrl) return '';
30
-
31
- const apiKey = state.selectedApiKey ? findApiKeyById(apiKeys, state.selectedApiKey) : null;
32
- const headers = parseRequestHeaders(state.requestHeaders);
33
-
34
- if (apiKey) {
35
- headers['X-API-Key'] = apiKey.id || ''; // Note: using id as full key not available
36
- }
37
-
38
- let curl = `curl -X ${state.requestMethod} "${state.requestUrl}"`;
39
-
40
- // Add headers
41
- Object.entries(headers).forEach(([key, value]) => {
42
- curl += ` \\\n -H "${key}: ${value}"`;
43
- });
44
-
45
- // Add body for non-GET requests
46
- if (state.requestBody && state.requestMethod !== 'GET' && isJsonValid) {
47
- curl += ` \\\n -d '${state.requestBody}'`;
48
- }
49
-
50
- return curl;
51
- };
52
-
53
- const curlCommand = generateCurlCommand();
54
-
55
- const handleSendRequest = useCallback(async () => {
56
- await sendRequest();
57
- }, [sendRequest]);
58
-
59
- return (
60
- <div className="space-y-4">
61
- {/* Endpoint Info */}
62
- {state.selectedEndpoint && <EndpointInfo />}
63
-
64
- {/* Request Parameters Form */}
65
- {state.selectedEndpoint && (
66
- <RequestParametersForm />
67
- )}
68
-
69
- {/* Request Body */}
70
- {state.requestMethod !== 'GET' && (
71
- <Card className="">
72
- <CardHeader>
73
- <CardTitle className="text-sm text-foreground">Request Body</CardTitle>
74
- </CardHeader>
75
- <CardContent>
76
- <Textarea
77
- placeholder='{\n "key": "value"\n}'
78
- value={state.requestBody}
79
- onChange={(e) => setRequestBody(e.target.value)}
80
- className={`font-mono text-sm ${
81
- !isJsonValid ? 'border-destructive' : ''
82
- }`}
83
- rows={6}
84
- />
85
- {!isJsonValid && (
86
- <p className="mt-1 text-xs text-destructive">Invalid JSON format</p>
87
- )}
88
- </CardContent>
89
- </Card>
90
- )}
91
-
92
- {/* Bearer Token Authentication */}
93
- <Card className="">
94
- <CardHeader>
95
- <CardTitle className="flex items-center space-x-2 text-sm text-foreground">
96
- <Key className="h-4 w-4" />
97
- <span>Bearer Token</span>
98
- </CardTitle>
99
- </CardHeader>
100
- <CardContent>
101
- <div className="flex flex-col sm:flex-row sm:items-center gap-2">
102
- <div className="flex-1">
103
- <Input
104
- type="password"
105
- placeholder="Enter Bearer token (optional, uses JWT if empty)"
106
- value={state.manualApiToken}
107
- onChange={(e) => setManualApiToken(e.target.value)}
108
- className="font-mono text-sm"
109
- />
110
- <p className="mt-1 text-xs text-muted-foreground">
111
- Leave empty to use JWT token from localStorage
112
- </p>
113
- </div>
114
- <Button
115
- onClick={handleSendRequest}
116
- disabled={state.loading || !state.requestUrl || !isJsonValid}
117
- className="bg-primary hover:bg-primary/90 text-primary-foreground flex items-center gap-2"
118
- >
119
- {state.loading ? (
120
- <>
121
- <Loader2 className="h-4 w-4 animate-spin" />
122
- <span className="hidden sm:inline">Sending...</span>
123
- </>
124
- ) : (
125
- <>
126
- <Send className="h-4 w-4" />
127
- <span className="hidden sm:inline">Send Request</span>
128
- </>
129
- )}
130
- </Button>
131
- </div>
132
- </CardContent>
133
- </Card>
134
-
135
- {/* cURL Command */}
136
- {curlCommand && (
137
- <Card>
138
- <CardHeader>
139
- <div className="flex items-center justify-between">
140
- <CardTitle className="text-sm text-foreground">cURL Command</CardTitle>
141
- <CopyButton
142
- value={curlCommand}
143
- variant="outline"
144
- size="sm"
145
- >
146
- Copy cURL
147
- </CopyButton>
148
- </div>
149
- </CardHeader>
150
- <CardContent>
151
- <PrettyCode data={curlCommand} language="bash" />
152
- </CardContent>
153
- </Card>
154
- )}
155
- </div>
156
- );
157
- };
@@ -1,253 +0,0 @@
1
- 'use client';
2
-
3
- import { HelpCircle } from 'lucide-react';
4
- import React, { useEffect, useState } from 'react';
5
-
6
- import {
7
- Accordion, AccordionContent, AccordionItem, AccordionTrigger, Badge, Button, Card, CardContent,
8
- CardHeader, CardTitle, Input, Label, Tabs, TabsContent, TabsList, TabsTrigger, Textarea,
9
- Tooltip, TooltipContent, TooltipProvider, TooltipTrigger
10
- } from '@djangocfg/ui-core/components';
11
-
12
- import { usePlaygroundContext } from '../context/PlaygroundContext';
13
-
14
- interface Parameter {
15
- name: string;
16
- type: string;
17
- required: boolean;
18
- description?: string;
19
- }
20
-
21
- export const RequestParametersForm: React.FC = () => {
22
- const { state, setParameters } = usePlaygroundContext();
23
- const [formData, setFormData] = useState<Record<string, string>>({});
24
- const [activeTab, setActiveTab] = useState('required');
25
-
26
- // Reset form data when endpoint changes
27
- useEffect(() => {
28
- if (state.selectedEndpoint?.parameters) {
29
- const initialData: Record<string, string> = {};
30
- state.selectedEndpoint.parameters.forEach(param => {
31
- initialData[param.name] = '';
32
- });
33
- setFormData(initialData);
34
- }
35
- }, [state.selectedEndpoint?.path, state.selectedEndpoint?.parameters]);
36
-
37
- const handleInputChange = (name: string, value: string) => {
38
- const newFormData = {
39
- ...formData,
40
- [name]: value
41
- };
42
- setFormData(newFormData);
43
-
44
- // Update parameters in context for URL substitution
45
- setParameters(newFormData);
46
- };
47
-
48
- const handleClearForm = () => {
49
- setFormData({});
50
- setParameters({});
51
- };
52
-
53
- const handleGenerateJson = () => {
54
- const hasData = Object.values(formData).some(value => value !== '' && value !== null && value !== undefined);
55
- if (hasData) {
56
- const jsonString = JSON.stringify(formData, null, 2);
57
- navigator.clipboard.writeText(jsonString);
58
- }
59
- };
60
-
61
- if (!state.selectedEndpoint) return null;
62
-
63
- const requiredParams = state.selectedEndpoint.parameters?.filter(param => param.required) || [];
64
- const optionalParams = state.selectedEndpoint.parameters?.filter(param => !param.required) || [];
65
- const hasParameters = (state.selectedEndpoint.parameters?.length || 0) > 0;
66
-
67
- if (!hasParameters) {
68
- return null;
69
- }
70
-
71
- const hasFormData = Object.values(formData).some(value => value !== '' && value !== null && value !== undefined);
72
-
73
- // Component for rendering parameter fields
74
- const ParameterField = ({ param }: { param: Parameter }) => (
75
- <div className="space-y-2">
76
- <div className="flex items-center space-x-1">
77
- <Label htmlFor={param.name} className="text-xs font-medium text-foreground">
78
- {param.name}
79
- {param.required && <span className="text-destructive ml-1">*</span>}
80
- </Label>
81
- {param.description && (
82
- <TooltipProvider>
83
- <Tooltip>
84
- <TooltipTrigger asChild>
85
- <HelpCircle className="h-3 w-3 text-muted-foreground hover:text-foreground cursor-help" />
86
- </TooltipTrigger>
87
- <TooltipContent side="top" className="max-w-xs">
88
- <p className="text-xs">{param.description}</p>
89
- </TooltipContent>
90
- </Tooltip>
91
- </TooltipProvider>
92
- )}
93
- </div>
94
- <Input
95
- id={param.name}
96
- value={formData[param.name] || ''}
97
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleInputChange(param.name, e.target.value)}
98
- placeholder={`Enter ${param.name}${param.required ? '' : ' (optional)'}`}
99
- className="text-sm"
100
- />
101
- </div>
102
- );
103
-
104
- // Component for rendering parameters in grid or list layout
105
- const ParametersGrid = ({ params, title, badgeVariant }: {
106
- params: Parameter[],
107
- title: string,
108
- badgeVariant: 'destructive' | 'secondary'
109
- }) => {
110
- if (params.length === 0) return null;
111
-
112
- return (
113
- <div className="space-y-3">
114
- <div className="flex items-center justify-between">
115
- <div className="flex items-center space-x-2">
116
- <Badge variant={badgeVariant} className="text-xs">{title}</Badge>
117
- <span className="text-xs text-muted-foreground">
118
- {params.length} parameter{params.length !== 1 ? 's' : ''}
119
- </span>
120
- </div>
121
- </div>
122
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
123
- {params.map((param) => (
124
- <ParameterField key={param.name} param={param} />
125
- ))}
126
- </div>
127
- </div>
128
- );
129
- };
130
-
131
- return (
132
- <TooltipProvider>
133
- <Card>
134
- <CardHeader className="pb-3">
135
- <div className="flex items-center justify-between">
136
- <CardTitle className="text-sm text-foreground">Request Parameters</CardTitle>
137
- <div className="flex items-center space-x-2">
138
- {hasFormData && (
139
- <Button
140
- variant="default"
141
- size="sm"
142
- onClick={handleGenerateJson}
143
- className="text-xs"
144
- >
145
- Copy JSON
146
- </Button>
147
- )}
148
- <Button
149
- variant="outline"
150
- size="sm"
151
- onClick={handleClearForm}
152
- className="text-xs"
153
- >
154
- Clear
155
- </Button>
156
- </div>
157
- </div>
158
- </CardHeader>
159
-
160
- <CardContent className="space-y-4">
161
- {/* Use Tabs for better organization when there are many parameters */}
162
- {(requiredParams.length + optionalParams.length) > 10 ? (
163
- <Tabs value={activeTab} onValueChange={setActiveTab}>
164
- <TabsList className="grid w-full grid-cols-2">
165
- <TabsTrigger value="required" className="text-xs">
166
- Required ({requiredParams.length})
167
- </TabsTrigger>
168
- <TabsTrigger value="optional" className="text-xs">
169
- Optional ({optionalParams.length})
170
- </TabsTrigger>
171
- </TabsList>
172
-
173
- <TabsContent value="required" className="space-y-4">
174
- <ParametersGrid
175
- params={requiredParams}
176
- title="Required"
177
- badgeVariant="destructive"
178
- />
179
- </TabsContent>
180
-
181
- <TabsContent value="optional" className="space-y-4">
182
- <ParametersGrid
183
- params={optionalParams}
184
- title="Optional"
185
- badgeVariant="secondary"
186
- />
187
- </TabsContent>
188
- </Tabs>
189
- ) : (
190
- /* Use Accordion for smaller parameter sets */
191
- <Accordion type="multiple" defaultValue={requiredParams.length > 0 ? ["required"] : ["optional"]}>
192
- {requiredParams.length > 0 && (
193
- <AccordionItem value="required">
194
- <AccordionTrigger className="text-sm text-foreground">
195
- <div className="flex items-center space-x-2">
196
- <Badge variant="destructive" className="text-xs">Required</Badge>
197
- <span className="text-xs text-muted-foreground">
198
- {requiredParams.length} parameter{requiredParams.length !== 1 ? 's' : ''}
199
- </span>
200
- </div>
201
- </AccordionTrigger>
202
- <AccordionContent>
203
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pt-2">
204
- {requiredParams.map((param) => (
205
- <ParameterField key={param.name} param={param} />
206
- ))}
207
- </div>
208
- </AccordionContent>
209
- </AccordionItem>
210
- )}
211
-
212
- {optionalParams.length > 0 && (
213
- <AccordionItem value="optional">
214
- <AccordionTrigger className="text-sm text-foreground">
215
- <div className="flex items-center space-x-2">
216
- <Badge variant="secondary" className="text-xs">Optional</Badge>
217
- <span className="text-xs text-muted-foreground">
218
- {optionalParams.length} parameter{optionalParams.length !== 1 ? 's' : ''}
219
- </span>
220
- </div>
221
- </AccordionTrigger>
222
- <AccordionContent>
223
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pt-2">
224
- {optionalParams.map((param) => (
225
- <ParameterField key={param.name} param={param} />
226
- ))}
227
- </div>
228
- </AccordionContent>
229
- </AccordionItem>
230
- )}
231
- </Accordion>
232
- )}
233
-
234
- {/* JSON Preview */}
235
- {hasFormData && (
236
- <div className="space-y-2 mt-6 pt-4 border-t">
237
- <Label className="text-xs text-foreground">Generated JSON</Label>
238
- <Textarea
239
- value={JSON.stringify(formData, null, 2)}
240
- readOnly
241
- className="text-xs font-mono"
242
- rows={4}
243
- />
244
- <p className="text-xs text-muted-foreground">
245
- Click &quot;Copy JSON&quot; to copy this to clipboard, then paste it into the Request Body field above.
246
- </p>
247
- </div>
248
- )}
249
- </CardContent>
250
- </Card>
251
- </TooltipProvider>
252
- );
253
- };
@@ -1,173 +0,0 @@
1
- 'use client';
2
-
3
- import { Download, XCircle } from 'lucide-react';
4
- import React, { useCallback, useMemo } from 'react';
5
-
6
- import {
7
- Badge, Button, Card, CardContent, CardHeader, CardTitle, CopyButton
8
- } from '@djangocfg/ui-core/components';
9
-
10
- import JsonTree from '../../JsonTree';
11
- import { usePlaygroundContext } from '../context/PlaygroundContext';
12
- import { getStatusColor } from '../utils';
13
-
14
- export const ResponseViewer: React.FC = () => {
15
- const { state } = usePlaygroundContext();
16
- const { response } = state;
17
-
18
- // Memoize response text for copy/download
19
- const responseText = useMemo(() => {
20
- if (!response?.data) return '';
21
- return typeof response.data === 'string' ? response.data : JSON.stringify(response.data, null, 2);
22
- }, [response?.data]);
23
-
24
- const handleDownloadResponse = useCallback(() => {
25
- if (!responseText) return;
26
- const blob = new Blob([responseText], { type: 'application/json' });
27
- const url = URL.createObjectURL(blob);
28
- const a = document.createElement('a');
29
- a.href = url;
30
- a.download = 'response.json';
31
- document.body.appendChild(a);
32
- a.click();
33
- document.body.removeChild(a);
34
- URL.revokeObjectURL(url);
35
- }, [responseText]);
36
-
37
- if (!response) {
38
- return (
39
- <div className="space-y-4">
40
- <div className="flex items-center justify-between">
41
- <h2 className="text-lg font-semibold text-foreground">Response</h2>
42
- </div>
43
- <Card>
44
- <CardContent className="flex items-center justify-center py-8">
45
- <p className="text-sm text-muted-foreground">No response yet. Send a request to see the response here.</p>
46
- </CardContent>
47
- </Card>
48
- </div>
49
- );
50
- }
51
-
52
- return (
53
- <div className="space-y-4">
54
- <div className="flex items-center justify-between">
55
- <h2 className="text-lg font-semibold text-foreground">Response</h2>
56
- <div className="flex items-center space-x-2">
57
- <CopyButton value={responseText} variant="outline" size="sm">
58
- Copy
59
- </CopyButton>
60
- <Button variant="outline" size="sm" onClick={handleDownloadResponse}>
61
- <Download className="h-4 w-4" />
62
- <span className="hidden sm:inline">Download</span>
63
- </Button>
64
- </div>
65
- </div>
66
-
67
- {/* Response Status */}
68
- <Card>
69
- <CardHeader>
70
- <CardTitle className="text-sm text-foreground">Response Information</CardTitle>
71
- </CardHeader>
72
- <CardContent className="space-y-4">
73
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
74
- <div className="space-y-2">
75
- <div className="flex items-center justify-between">
76
- <span className="text-sm font-medium text-foreground">Status Code:</span>
77
- <Badge
78
- variant={getStatusColor(response.status || 0) === 'success' ? 'default' :
79
- getStatusColor(response.status || 0) === 'error' ? 'destructive' : 'secondary'}
80
- className="text-xs"
81
- >
82
- {response.status}
83
- </Badge>
84
- </div>
85
- {response.statusText && (
86
- <div className="flex items-center justify-between">
87
- <span className="text-sm font-medium text-foreground">Status Text:</span>
88
- <span className="text-sm text-muted-foreground">{response.statusText}</span>
89
- </div>
90
- )}
91
- </div>
92
-
93
- <div className="space-y-2">
94
- <div className="flex items-center justify-between">
95
- <span className="text-sm font-medium text-foreground">Response Type:</span>
96
- <span className="text-sm text-muted-foreground">
97
- {typeof response.data === 'string' ? 'Text' : 'JSON'}
98
- </span>
99
- </div>
100
- <div className="flex items-center justify-between">
101
- <span className="text-sm font-medium text-foreground">Data Size:</span>
102
- <span className="text-sm text-muted-foreground">
103
- {typeof response.data === 'string' ? response.data.length : JSON.stringify(response.data).length} chars
104
- </span>
105
- </div>
106
- </div>
107
- </div>
108
-
109
- {response.error && (
110
- <div className="bg-destructive/10 border border-destructive/20 rounded p-3">
111
- <div className="flex items-center space-x-2">
112
- <XCircle className="h-4 w-4 text-destructive" />
113
- <p className="text-sm text-destructive">{response.error}</p>
114
- </div>
115
- </div>
116
- )}
117
- </CardContent>
118
- </Card>
119
-
120
- {/* Response Body */}
121
- <Card>
122
- <CardContent className="p-0">
123
- {(() => {
124
- // Check if response body should be hidden
125
- const is404 = response.status === 404;
126
- const isNonJson = typeof response.data === 'string';
127
-
128
- if (is404 || isNonJson) {
129
- return (
130
- <div className="p-8 text-center">
131
- <p className="text-sm text-muted-foreground">
132
- {is404
133
- ? 'Response body hidden for 404 Not Found status'
134
- : 'Response body hidden for non-JSON responses'}
135
- </p>
136
- </div>
137
- );
138
- }
139
-
140
- if (response.data) {
141
- return (
142
- <JsonTree
143
- title="Response Body"
144
- data={response.data}
145
- config={{
146
- // Smart defaults for API responses
147
- maxAutoExpandDepth: 2,
148
- maxAutoExpandArrayItems: 10,
149
- maxAutoExpandObjectKeys: 5,
150
- maxStringLength: 200,
151
- collectionLimit: 50,
152
- showCollectionInfo: true,
153
- showExpandControls: true,
154
- showActionButtons: false, // We have our own copy/download buttons above
155
- preserveKeyOrder: true,
156
- className: "border-0 rounded-none"
157
- }}
158
- />
159
- );
160
- }
161
-
162
- return (
163
- <div className="p-8 text-center">
164
- <p className="text-sm text-muted-foreground">No response body available</p>
165
- </div>
166
- );
167
- })()}
168
- </CardContent>
169
- </Card>
170
-
171
- </div>
172
- );
173
- };
@@ -1,68 +0,0 @@
1
- 'use client';
2
-
3
- import { GitBranch, Info } from 'lucide-react';
4
- import React from 'react';
5
-
6
- import {
7
- Badge, Select, SelectContent, SelectItem, SelectTrigger, SelectValue
8
- } from '@djangocfg/ui-core/components';
9
-
10
- import { usePlaygroundContext } from '../context/PlaygroundContext';
11
- import useOpenApiSchema from '../hooks/useOpenApiSchema';
12
- import { API_VERSIONS, getVersionById, getVersionStats } from '../utils/versionManager';
13
-
14
- export const VersionSelector: React.FC = () => {
15
- const { state, config, setSelectedVersion } = usePlaygroundContext();
16
- const { endpoints } = useOpenApiSchema({
17
- schemas: config.schemas,
18
- defaultSchemaId: config.defaultSchemaId,
19
- });
20
-
21
- const currentVersion = getVersionById(state.selectedVersion);
22
- const versionStats = getVersionStats(endpoints);
23
-
24
- const handleVersionChange = (versionId: string) => {
25
- setSelectedVersion(versionId);
26
- };
27
-
28
- return (
29
- <div className="flex items-center space-x-3">
30
- <div className="flex items-center space-x-2">
31
- <GitBranch className="h-4 w-4 text-muted-foreground" />
32
- <span className="text-sm font-medium text-foreground">API Version:</span>
33
- </div>
34
-
35
- <Select value={state.selectedVersion} onValueChange={handleVersionChange}>
36
- <SelectTrigger className="w-48">
37
- <SelectValue />
38
- </SelectTrigger>
39
- <SelectContent>
40
- {API_VERSIONS.map((version) => (
41
- <SelectItem key={version.id} value={version.id}>
42
- <div className="flex items-center justify-between w-full">
43
- <div className="flex items-center space-x-2">
44
- <span className="font-medium">{version.name}</span>
45
- {version.isDefault && (
46
- <Badge variant="secondary" className="text-xs">
47
- Default
48
- </Badge>
49
- )}
50
- </div>
51
- <span className="text-xs text-muted-foreground ml-2">
52
- {versionStats[version.id] || 0} endpoints
53
- </span>
54
- </div>
55
- </SelectItem>
56
- ))}
57
- </SelectContent>
58
- </Select>
59
-
60
- {currentVersion && (
61
- <div className="flex items-center space-x-1 text-xs text-muted-foreground">
62
- <Info className="h-3 w-3" />
63
- <span>{currentVersion.description}</span>
64
- </div>
65
- )}
66
- </div>
67
- );
68
- };