import { useState } from "react"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { PluginMetadata } from "@/client/hooks/usePlugin"; import { Play, ChevronDown, ChevronUp, Copy, Check } from "lucide-react"; import { CodeBlock } from "@/components/CodeBlock"; import { getApiUrl } from "@/lib/api-url"; interface PluginCardProps { plugin: PluginMetadata; } const methodColors: Record = { GET: "bg-green-500/20 text-green-400 border-green-500/50", POST: "bg-blue-500/20 text-blue-400 border-blue-500/50", PUT: "bg-yellow-500/20 text-yellow-400 border-yellow-500/50", DELETE: "bg-red-500/20 text-red-400 border-red-500/50", PATCH: "bg-purple-500/20 text-purple-400 border-purple-500/50", }; export function PluginCard({ plugin }: PluginCardProps) { const [paramValues, setParamValues] = useState>({}); const [response, setResponse] = useState(null); const [responseHeaders, setResponseHeaders] = useState>({}); const [requestUrl, setRequestUrl] = useState(""); const [loading, setLoading] = useState(false); const [isExpanded, setIsExpanded] = useState(false); const [copiedUrl, setCopiedUrl] = useState(false); const [copiedRequestUrl, setCopiedRequestUrl] = useState(false); const handleParamChange = (paramName: string, value: string) => { setParamValues((prev) => ({ ...prev, [paramName]: value })); }; const handleExecute = async () => { setLoading(true); try { let url = "/api" + plugin.endpoint; let fullUrl = getApiUrl(plugin.endpoint); if (plugin.method === "GET" && plugin.parameters?.query) { const queryParams = new URLSearchParams(); plugin.parameters.query.forEach((param) => { const value = paramValues[param.name]; if (value) { queryParams.append(param.name, value); } }); if (queryParams.toString()) { url += "?" + queryParams.toString(); fullUrl += "?" + queryParams.toString(); } } setRequestUrl(fullUrl); const fetchOptions: RequestInit = { method: plugin.method, }; if (["POST", "PUT", "PATCH"].includes(plugin.method) && plugin.parameters?.body) { const bodyData: Record = {}; plugin.parameters.body.forEach((param) => { const value = paramValues[param.name]; if (value) { bodyData[param.name] = value; } }); fetchOptions.body = JSON.stringify(bodyData); fetchOptions.headers = { "Content-Type": "application/json", }; } const res = await fetch(url, fetchOptions); const data = await res.json(); const headers: Record = {}; res.headers.forEach((value, key) => { headers[key] = value; }); setResponseHeaders(headers); setResponse({ status: res.status, statusText: res.statusText, data, }); } catch (error) { setResponse({ status: 500, statusText: "Error", data: { error: error instanceof Error ? error.message : "Unknown error" }, }); setResponseHeaders({}); } finally { setLoading(false); } }; const copyApiUrl = () => { const fullUrl = getApiUrl(plugin.endpoint); navigator.clipboard.writeText(fullUrl); setCopiedUrl(true); setTimeout(() => setCopiedUrl(false), 2000); }; const copyRequestUrl = () => { navigator.clipboard.writeText(requestUrl); setCopiedRequestUrl(true); setTimeout(() => setCopiedRequestUrl(false), 2000); }; const renderParameterInput = (param: any) => { if (param.enum && Array.isArray(param.enum) && param.enum.length > 0) { return ( ); } else { return ( handleParamChange(param.name, e.target.value)} className="bg-black/50 border-white/10 text-white focus:border-purple-500" /> ); } }; const hasQueryParams = plugin.parameters?.query && plugin.parameters.query.length > 0; const hasBodyParams = plugin.parameters?.body && plugin.parameters.body.length > 0; const hasPathParams = plugin.parameters?.path && plugin.parameters.path.length > 0; const hasAnyParams = hasQueryParams || hasBodyParams || hasPathParams; const generateCurlExample = () => { let curl = `curl -X ${plugin.method} "${getApiUrl(plugin.endpoint)}`; if (hasQueryParams) { const exampleParams = plugin.parameters!.query! .map((p) => `${p.name}=${p.example || 'value'}`) .join('&'); curl += `?${exampleParams}`; } curl += '"'; if (hasBodyParams) { curl += ' \\\n -H "Content-Type: application/json" \\\n -d \''; const bodyExample: Record = {}; plugin.parameters!.body!.forEach((p) => { bodyExample[p.name] = p.example || 'value'; }); curl += JSON.stringify(bodyExample, null, 2); curl += "'"; } return curl; }; const generateNodeExample = () => { let code = `const response = await fetch("${getApiUrl(plugin.endpoint)}`; if (hasQueryParams) { const exampleParams = plugin.parameters!.query! .map((p) => `${p.name}=${p.example || 'value'}`) .join('&'); code += `?${exampleParams}`; } code += '", {\n method: "' + plugin.method + '"'; if (hasBodyParams) { code += ',\n headers: {\n "Content-Type": "application/json"\n },\n body: JSON.stringify('; const bodyExample: Record = {}; plugin.parameters!.body!.forEach((p) => { bodyExample[p.name] = p.example || 'value'; }); code += JSON.stringify(bodyExample, null, 2); code += ')'; } code += '\n});\n\nconst data = await response.json();\nconsole.log(data);'; return code; }; return ( {/* Collapsible Header */}
setIsExpanded(!isExpanded)} >
{plugin.method} {plugin.endpoint}

{plugin.name}

{plugin.description}

{/* Tags */} {plugin.tags && plugin.tags.length > 0 && (
{plugin.tags.map((tag) => ( {tag} ))}
)} {/* API URL Display */}
API URL: {getApiUrl(plugin.endpoint)}
{/* Expandable Content */} {isExpanded && ( Documentation Try It Out {/* Documentation Tab */} {/* Parameters Table */} {hasAnyParams && (

Parameters

{/* Path Parameters */} {plugin.parameters?.path?.map((param) => ( ))} {/* Query Parameters */} {plugin.parameters?.query?.map((param) => ( ))} {/* Body Parameters */} {plugin.parameters?.body?.map((param) => ( ))}
Name Type Required Description
{param.name} {param.type} {param.enum && ( (options: {param.enum.join(', ')}) )} {param.required ? "Yes" : "No"} {param.description}
{param.name} {param.type} {param.enum && ( (options: {param.enum.join(', ')}) )} {param.required ? "Yes" : "No"} {param.description}
{param.name} {param.type} {param.enum && ( (options: {param.enum.join(', ')}) )} {param.required ? "Yes" : "No"} {param.description}
)} {/* Responses */} {plugin.responses && Object.keys(plugin.responses).length > 0 && (

Responses

{Object.entries(plugin.responses).map(([status, response]) => (
= 200 && parseInt(status) < 300 ? "bg-green-500/10" : parseInt(status) >= 400 && parseInt(status) < 500 ? "bg-yellow-500/10" : "bg-red-500/10" }`}> = 200 && parseInt(status) < 300 ? "bg-green-500/20 text-green-400 border-green-500/50" : parseInt(status) >= 400 && parseInt(status) < 500 ? "bg-yellow-500/20 text-yellow-400 border-yellow-500/50" : "bg-red-500/20 text-red-400 border-red-500/50" } border font-bold`} > {status} {response.description}
                        {JSON.stringify(response.example, null, 2)}
                      
))}
)} {/* Code Examples */}

Code Example

cURL
Node.js (fetch)
{/* Try It Out Tab */} {/* Parameters Input */} {hasAnyParams ? (
{/* Query Parameters */} {plugin.parameters?.query?.map((param) => (
{renderParameterInput(param)}

{param.description}

))} {/* Body Parameters */} {plugin.parameters?.body?.map((param) => (
{renderParameterInput(param)}

{param.description}

))}
) : (

No parameters required

)} {/* Execute Button */} {/* Response Display */} {response && (
{/* Request URL */}
Request URL
{requestUrl}
{/* Response Status */}
Response Status = 200 && response.status < 300 ? "bg-green-500/20 text-green-400" : "bg-red-500/20 text-red-400" }`}> {response.status} {response.statusText}
{/* Response Headers */} {Object.keys(responseHeaders).length > 0 && (
Response Headers
{Object.entries(responseHeaders).map(([key, value]) => (
{key}:{" "} {value}
))}
)} {/* Response Body with Syntax Highlighting */}
Response Body
)}
)}
); }