banatie-service/apps/landing/src/components/docs/variant-c/InteractiveAPIWidgetC.tsx

394 lines
14 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
/**
* Interactive API Widget - Variant C: Modern & Visual (Shopify-inspired)
*
* Design Philosophy: Floating, expandable card with colorful visual feedback
*
* Features:
* - Full-screen expandable mode toggle button
* - Large gradient-bordered card with shadow glow
* - Prominent "Test Now ⚡" button (larger, more visual)
* - Response in colorful gradient-bordered card
* - Success/error states with vibrant colored badges
* - More visual feedback and animations
* - Large emoji icons throughout
* - Colorful code syntax highlighting hints
* - Floating shadow effects
*
* Layout:
* - Top: Large colorful API key input card
* - Main: Expandable code editor with rainbow gradient border
* - Language tabs as colorful pills
* - Large prominent "Test Now" button with gradient
* - Response card with colored status indicators
*
* Visual Elements:
* - Purple/cyan/amber gradients everywhere
* - Large shadows with colored glows
* - Scale and hover animations
* - Success: Green gradient with glow
* - Error: Red gradient with glow
*/
import { useState, useEffect } from 'react';
interface InteractiveAPIWidgetCProps {
endpoint: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
description: string;
parameters?: Array<{
name: string;
type: string;
required: boolean;
description: string;
defaultValue?: string;
}>;
}
type Language = 'curl' | 'javascript' | 'python' | 'go';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
const API_KEY_STORAGE = 'banatie_docs_api_key';
const getMethodColor = (method: string): string => {
switch (method) {
case 'GET':
return 'from-cyan-500 to-cyan-600';
case 'POST':
return 'from-purple-500 to-purple-600';
case 'PUT':
return 'from-amber-500 to-amber-600';
case 'DELETE':
return 'from-red-500 to-red-600';
default:
return 'from-purple-500 to-cyan-500';
}
};
export const InteractiveAPIWidgetC = ({
endpoint,
method,
description,
parameters = [],
}: InteractiveAPIWidgetCProps) => {
const [language, setLanguage] = useState<Language>('curl');
const [apiKey, setApiKey] = useState('');
const [isExecuting, setIsExecuting] = useState(false);
const [response, setResponse] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
const [paramValues, setParamValues] = useState<Record<string, string>>({});
const [isExpanded, setIsExpanded] = useState(false);
useEffect(() => {
const stored = localStorage.getItem(API_KEY_STORAGE);
if (stored) setApiKey(stored);
const defaults: Record<string, string> = {};
parameters.forEach((param) => {
if (param.defaultValue) {
defaults[param.name] = param.defaultValue;
}
});
setParamValues(defaults);
}, [parameters]);
const handleApiKeyChange = (value: string) => {
setApiKey(value);
if (value) {
localStorage.setItem(API_KEY_STORAGE, value);
} else {
localStorage.removeItem(API_KEY_STORAGE);
}
};
const generateCode = (): string => {
const url = `${API_BASE_URL}${endpoint}`;
switch (language) {
case 'curl':
return `curl -X ${method} "${url}" \\
-H "X-API-Key: ${apiKey || 'YOUR_API_KEY'}" \\
-H "Content-Type: application/json" \\
-d '{
"prompt": "a futuristic city at sunset",
"filename": "city",
"aspectRatio": "16:9"
}'`;
case 'javascript':
return `const response = await fetch('${url}', {
method: '${method}',
headers: {
'X-API-Key': '${apiKey || 'YOUR_API_KEY'}',
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: 'a futuristic city at sunset',
filename: 'city',
aspectRatio: '16:9'
})
});
const data = await response.json();`;
case 'python':
return `import requests
url = '${url}'
headers = {
'X-API-Key': '${apiKey || 'YOUR_API_KEY'}',
'Content-Type': 'application/json'
}
data = {
'prompt': 'a futuristic city at sunset',
'filename': 'city',
'aspectRatio': '16:9'
}
response = requests.${method.toLowerCase()}(url, headers=headers, json=data)`;
case 'go':
return `package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
url := "${url}"
data := map[string]interface{}{
"prompt": "a futuristic city at sunset",
"filename": "city",
"aspectRatio": "16:9",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("${method}", url, bytes.NewBuffer(jsonData))
req.Header.Set("X-API-Key", "${apiKey || 'YOUR_API_KEY'}")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
}`;
default:
return '';
}
};
const executeRequest = async () => {
if (!apiKey) {
setError('Please enter your API key above ✨');
return;
}
setIsExecuting(true);
setError(null);
try {
const body: Record<string, any> = {};
parameters.forEach((param) => {
if (paramValues[param.name]) {
body[param.name] = paramValues[param.name];
}
});
const res = await fetch(`${API_BASE_URL}${endpoint}`, {
method,
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
},
body: method !== 'GET' ? JSON.stringify(body) : undefined,
});
const data = await res.json();
setResponse(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to execute request 😔');
} finally {
setIsExecuting(false);
}
};
const copyCode = () => {
navigator.clipboard.writeText(generateCode());
};
return (
<div className="my-12">
{/* API Key Card - Large and Colorful */}
<div className="mb-6 p-6 bg-gradient-to-br from-purple-500/10 to-cyan-500/10 backdrop-blur-sm border-2 border-purple-500/40 rounded-2xl shadow-xl shadow-purple-500/20">
<div className="flex items-center gap-4 mb-3">
<span className="text-2xl">🔑</span>
<label className="text-sm font-bold text-white">Your API Key</label>
</div>
<div className="flex items-center gap-3">
<input
type="password"
value={apiKey}
onChange={(e) => handleApiKeyChange(e.target.value)}
placeholder="Enter your API key (saved locally)"
className="flex-1 px-4 py-3 text-sm bg-slate-900/80 border-2 border-purple-500/30 rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500/50 transition-all"
/>
<div
className={`px-4 py-3 rounded-xl font-semibold text-sm transition-all ${
apiKey
? 'bg-green-500/20 text-green-400 border-2 border-green-500/40 shadow-lg shadow-green-500/20'
: 'bg-gray-500/20 text-gray-400 border-2 border-gray-500/20'
}`}
>
{apiKey ? '✓ Ready' : '⚠️ Required'}
</div>
</div>
</div>
{/* Main Widget Card - Large Gradient Border */}
<div
className={`bg-gradient-to-br from-slate-900/80 to-slate-950/80 backdrop-blur-sm border-2 border-purple-500/40 rounded-2xl overflow-hidden shadow-2xl shadow-purple-500/30 transition-all duration-500 ${
isExpanded ? 'fixed inset-4 z-50' : ''
}`}
>
{/* Header with Expand Button */}
<div className="flex items-center justify-between bg-slate-950/80 px-6 py-4 border-b-2 border-purple-500/30">
<div className="flex items-center gap-3">
<div className={`px-3 py-1.5 rounded-lg bg-gradient-to-r ${getMethodColor(method)} text-white text-sm font-bold shadow-lg`}>
{method}
</div>
<span className="text-sm text-gray-400">{endpoint}</span>
</div>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="px-4 py-2 bg-cyan-600/20 hover:bg-cyan-600/30 text-cyan-400 rounded-lg transition-all text-sm font-semibold border-2 border-cyan-500/40 hover:border-cyan-500/60"
>
{isExpanded ? '✕ Close' : '⛶ Expand'}
</button>
</div>
<div className="grid md:grid-cols-2 gap-6 p-6">
{/* Left: Code Editor */}
<div>
{/* Language Tabs - Colorful Pills */}
<div className="flex items-center gap-2 mb-4">
{(['curl', 'javascript', 'python', 'go'] as Language[]).map((lang, idx) => {
const colors = ['purple', 'cyan', 'amber', 'purple'];
const color = colors[idx];
return (
<button
key={lang}
onClick={() => setLanguage(lang)}
className={`px-4 py-2 text-sm rounded-xl font-semibold transition-all border-2 ${
language === lang
? color === 'purple'
? 'bg-purple-500/30 text-white border-purple-500/60 shadow-lg shadow-purple-500/30'
: color === 'cyan'
? 'bg-cyan-500/30 text-white border-cyan-500/60 shadow-lg shadow-cyan-500/30'
: 'bg-amber-500/30 text-white border-amber-500/60 shadow-lg shadow-amber-500/30'
: 'bg-slate-800/30 text-gray-400 border-slate-700 hover:border-slate-600 hover:text-white'
}`}
>
{lang === 'javascript' ? 'JS' : lang.toUpperCase()}
</button>
);
})}
</div>
{/* Code Display - Gradient Border */}
<div className="border-2 border-cyan-500/30 rounded-xl overflow-hidden shadow-lg">
<div className="flex items-center justify-between bg-slate-950/80 px-4 py-3 border-b-2 border-cyan-500/20">
<span className="text-xs text-cyan-400 font-semibold">Code Example</span>
<button
onClick={copyCode}
className="px-3 py-1.5 text-xs bg-purple-600/30 hover:bg-purple-600/50 text-purple-300 rounded-lg transition-all font-semibold"
>
📋 Copy
</button>
</div>
<div className="p-4 bg-slate-950/50 h-80 overflow-auto">
<pre className="text-xs text-gray-300 leading-relaxed">
<code>{generateCode()}</code>
</pre>
</div>
</div>
</div>
{/* Right: Response */}
<div>
<h4 className="text-sm font-bold text-white mb-4 flex items-center gap-2">
<span className="text-xl">📦</span>
Response
</h4>
<div className="border-2 border-purple-500/30 rounded-xl overflow-hidden shadow-lg">
<div className="bg-slate-950/80 px-4 py-3 border-b-2 border-purple-500/20">
{response && (
<span
className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-bold ${
response.success
? 'bg-green-500/20 text-green-400 border-2 border-green-500/40 shadow-lg shadow-green-500/20'
: 'bg-red-500/20 text-red-400 border-2 border-red-500/40 shadow-lg shadow-red-500/20'
}`}
>
{response.success ? '✓ Success' : '✕ Error'}
</span>
)}
</div>
<div className="p-4 bg-slate-950/50 h-80 overflow-auto">
{error ? (
<div className="p-4 bg-red-500/10 border-2 border-red-500/40 rounded-xl text-red-400 text-sm shadow-lg shadow-red-500/20">
<div className="flex items-start gap-2">
<span className="text-xl"></span>
<div>
<p className="font-semibold mb-1">Error</p>
<p className="text-xs">{error}</p>
</div>
</div>
</div>
) : response ? (
<pre className="text-xs text-gray-300 leading-relaxed">
<code>{JSON.stringify(response, null, 2)}</code>
</pre>
) : (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<p className="text-2xl mb-3">🚀</p>
<p className="text-sm text-gray-500">
Click <strong className="text-purple-400">"Test Now"</strong> below to see the response
</p>
</div>
</div>
)}
</div>
</div>
</div>
</div>
{/* Large Test Now Button */}
<div className="px-6 pb-6">
<button
onClick={executeRequest}
disabled={!apiKey || isExecuting}
className="w-full px-8 py-4 rounded-xl bg-gradient-to-r from-purple-600 to-cyan-600 text-white text-base font-bold hover:from-purple-500 hover:to-cyan-500 transition-all disabled:opacity-50 disabled:cursor-not-allowed shadow-2xl hover:shadow-purple-500/50 hover:scale-105 flex items-center justify-center gap-3 border-2 border-purple-500/50"
>
{isExecuting ? (
<>
<span className="animate-spin"></span>
<span>Executing...</span>
</>
) : (
<>
<span></span>
<span>Test Now</span>
</>
)}
</button>
<p className="mt-3 text-xs text-gray-500 text-center">{description}</p>
</div>
</div>
</div>
);
};