import * as React from "react"; import * as dropzone from "react-dropzone"; // import type { FieldProps } from "tinacms/dist/forms"; // Try default import from tinacms // import FieldProps from "tinacms"; // Fallback: restore the original TinaFieldProps interface if FieldProps cannot be imported interface TinaFieldProps { input: { value: string; onChange: (value: string) => void; }; field: { name: string; label?: string; description?: string; }; } const { useDropzone } = dropzone; // Define Swagger-related types interface SwaggerEndpoint { path: string; method: string; summary: string; description: string; operationId: string; tags: string[]; parameters: any[]; responses: Record; consumes: string[]; produces: string[]; security: any[]; } interface SwaggerInfo { title?: string; version?: string; description?: string; [key: string]: any; } interface SwaggerParseResult { endpoints: SwaggerEndpoint[]; info: SwaggerInfo | null; definitions?: Record; error: string | null; } // Utility function to parse Swagger JSON and extract endpoints export const parseSwaggerJson = (jsonString: string): SwaggerParseResult => { try { // Parse the JSON string into an object const swagger = JSON.parse(jsonString); // Check if it's a valid Swagger/OpenAPI document if (!swagger.paths) { return { endpoints: [], info: null, error: "Invalid Swagger JSON: Missing paths object", }; } // Extract basic API information const info = swagger.info || {}; // Extract endpoints from the paths object const endpoints: SwaggerEndpoint[] = []; for (const path of Object.keys(swagger.paths)) { const pathItem = swagger.paths[path]; // Process each HTTP method in the path (GET, POST, PUT, DELETE, etc.) for (const method of Object.keys(pathItem)) { const operation = pathItem[method]; // Create an endpoint object with relevant information const endpoint: SwaggerEndpoint = { path, method: method.toUpperCase(), summary: operation.summary || "", description: operation.description || "", operationId: operation.operationId || "", tags: operation.tags || [], parameters: operation.parameters || [], responses: operation.responses || {}, consumes: operation.consumes || [], produces: operation.produces || [], security: operation.security || [], }; endpoints.push(endpoint); } } return { endpoints, info, definitions: swagger.definitions || {}, error: null, }; } catch (error) { return { endpoints: [], info: null, error: error.message }; } }; // SVG icons as React components const FileIcon = (props: React.SVGProps) => ( ); // JSON file icon const JsonFileIcon = (props: React.SVGProps) => ( ); const FilePlusIcon = (props: React.SVGProps) => ( ); const TrashIcon = (props: React.SVGProps) => ( ); // File Preview component const FilePreview = ({ src }: { src: string }) => { const fileName = src.split("/").pop() || src; return (
{fileName} {src.startsWith("http") ? "Uploaded file" : src}
); }; // JSON file preview const JsonFilePreview = ({ fileName, jsonPreview, }: { fileName: string; jsonPreview?: string; }) => { return (
{fileName} {jsonPreview && ( {jsonPreview} )}
); }; // Loading indicator const LoadingIndicator = () => (
); // Delete button const DeleteFileButton = ({ onClick, }: { onClick: (_event: React.MouseEvent) => void; }) => { return ( ); }; // JSON File Upload component export const JsonFileUploadComponent = ({ input, field }: TinaFieldProps) => { const [loading, setLoading] = React.useState(false); const [fileName, setFileName] = React.useState(""); const [jsonPreview, setJsonPreview] = React.useState(""); const [swaggerData, setSwaggerData] = React.useState(null); React.useEffect(() => { // Parse existing data if available if (input.value && typeof input.value === "string" && input.value.trim()) { try { const data = parseSwaggerJson(input.value); setSwaggerData(data); // Show number of endpoints in the preview if (data.endpoints?.length > 0) { setJsonPreview( `Contains ${data.endpoints.length} endpoints. API: ${ data.info?.title || "Unknown" }` ); } } catch (error) { // Handle error silently } } }, [input.value]); const handleDrop = async (acceptedFiles: File[]) => { if (acceptedFiles.length === 0) return; setLoading(true); try { const file = acceptedFiles[0]; // Only accept JSON files if (file.type === "application/json" || file.name.endsWith(".json")) { const reader = new FileReader(); reader.onload = () => { try { const jsonContent = reader.result as string; // Parse the Swagger JSON to validate and extract info const swaggerData = parseSwaggerJson(jsonContent); setSwaggerData(swaggerData); if (swaggerData.error) { alert(`Error parsing Swagger JSON: ${swaggerData.error}`); setLoading(false); return; } // Store the complete JSON content in the input input.onChange(jsonContent); setFileName(file.name); // Create a meaningful preview let preview = ""; if (swaggerData.info?.title) { preview = `${swaggerData.info.title} v${ swaggerData.info.version || "unknown" } - `; } preview += `${swaggerData.endpoints.length} endpoints`; setJsonPreview(preview); // Log for debugging setLoading(false); } catch (parseError) { try { const parseError = new Error("Error handling JSON"); alert( "Error parsing JSON file. Please ensure it is a valid Swagger/OpenAPI JSON." ); setLoading(false); } catch (error) { setLoading(false); } } }; reader.onerror = () => { setLoading(false); }; reader.readAsText(file); // Read as text for JSON files } else { alert("Please upload a JSON file only"); setLoading(false); } } catch (error) { setLoading(false); } }; const handleClear = () => { input.onChange(""); setFileName(""); setJsonPreview(""); setSwaggerData(null); }; const handleBrowseClick = () => { // Trigger file browser document.getElementById(`file-upload-${field.name}`)?.click(); }; const { getRootProps, getInputProps, isDragActive } = useDropzone({ accept: { "application/json": [], "text/json": [], }, onDrop: handleDrop, noClick: true, // We'll handle clicks ourselves }); return (
{field.label && (

{field.label}

)} {field.description && (

{field.description}

)}
{input.value ? ( loading ? ( ) : (
{/* Show summary of parsed Swagger data */} {swaggerData && swaggerData.endpoints.length > 0 && (

API: {swaggerData.info?.title || "Unknown"}

Version: {swaggerData.info?.version || "Unknown"}

{swaggerData.endpoints.length} endpoints found

)}
{ e.stopPropagation(); handleClear(); }} />
) ) : ( )}
); };