Compare commits
No commits in common. "658f1420db40d1b4e4449cd0e2d56f4e7dea90b2" and "da476348059f9259d7746ed79726732130ba749b" have entirely different histories.
658f1420db
...
da47634805
|
|
@ -1,255 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* API Reference: Text to Image - Final Variant
|
||||
*
|
||||
* Features:
|
||||
* - SubsectionNav at top
|
||||
* - InteractiveAPIWidgetFinal with expand + success styling
|
||||
* - Enhanced tables for parameters and error codes
|
||||
* - Compact TipBox for parameter descriptions
|
||||
* - Simplified Next Steps
|
||||
*/
|
||||
|
||||
import { DocsLayoutFinal } from '@/components/docs/final/DocsLayoutFinal';
|
||||
import { DocsSidebarFinal } from '@/components/docs/final/DocsSidebarFinal';
|
||||
import { DocsTOCFinal } from '@/components/docs/final/DocsTOCFinal';
|
||||
import { SubsectionNav } from '@/components/shared/SubsectionNav';
|
||||
import { TipBox } from '@/components/docs/shared/TipBox';
|
||||
import { Table } from '@/components/docs/shared/Table';
|
||||
import { InteractiveAPIWidgetFinal } from '@/components/docs/final/InteractiveAPIWidgetFinal';
|
||||
import { Breadcrumb } from '@/components/docs/shared/Breadcrumb';
|
||||
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
|
||||
|
||||
const tocItems = [
|
||||
{ id: 'overview', text: 'Overview', level: 2 },
|
||||
{ id: 'endpoint', text: 'Endpoint', level: 2 },
|
||||
{ id: 'parameters', text: 'Parameters', level: 2 },
|
||||
{ id: 'response', text: 'Response', level: 2 },
|
||||
{ id: 'error-codes', text: 'Error Codes', level: 2 },
|
||||
{ id: 'interactive', text: 'Try It Live', level: 2 },
|
||||
{ id: 'next-steps', text: 'Next Steps', level: 2 },
|
||||
];
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Documentation', href: '/docs/final' },
|
||||
{ label: 'Demo', href: '/demo' },
|
||||
{ label: 'Examples', href: '/docs/final/examples' },
|
||||
];
|
||||
|
||||
const parameters = [
|
||||
{ name: 'prompt', type: 'string', required: true, description: 'Text description of the image to generate' },
|
||||
{ name: 'filename', type: 'string', required: false, description: 'Output filename (without extension)' },
|
||||
{ name: 'aspectRatio', type: 'string', required: false, description: 'Image aspect ratio: "1:1", "16:9", "9:16", "4:3"' },
|
||||
{ name: 'autoEnhance', type: 'boolean', required: false, description: 'Enable AI prompt enhancement for better results' },
|
||||
];
|
||||
|
||||
export default function TextToImageAPIPage() {
|
||||
return (
|
||||
<>
|
||||
{/* Subsection Navigation */}
|
||||
<SubsectionNav
|
||||
items={navItems}
|
||||
currentPath="/docs/final/api/text-to-image"
|
||||
ctaText="Join Beta"
|
||||
ctaHref="/signup"
|
||||
/>
|
||||
|
||||
<DocsLayoutFinal
|
||||
sidebar={<DocsSidebarFinal currentPath="/docs/final/api/text-to-image" />}
|
||||
toc={<DocsTOCFinal items={tocItems} />}
|
||||
>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{ label: 'Documentation', href: '/docs' },
|
||||
{ label: 'API Reference', href: '/docs/final/api' },
|
||||
{ label: 'Text to Image' },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="mb-12">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-white mb-4">Text to Image</h1>
|
||||
<p className="text-xl text-gray-400 leading-relaxed">
|
||||
Generate high-quality images from text prompts using AI-powered models.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Overview */}
|
||||
<section id="overview" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Overview</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
The Text to Image endpoint allows you to generate images from natural language descriptions.
|
||||
Powered by Google Gemini 2.5 Flash and Imagen 4.0, it produces photorealistic images
|
||||
optimized for your specified requirements.
|
||||
</p>
|
||||
<TipBox variant="compact" type="info">
|
||||
<strong>Tip:</strong> Enable <code className="px-1.5 py-0.5 bg-slate-800 rounded text-purple-400">autoEnhance</code>
|
||||
to let AI improve your prompts for better image quality.
|
||||
</TipBox>
|
||||
</section>
|
||||
|
||||
{/* Endpoint */}
|
||||
<section id="endpoint" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Endpoint</h2>
|
||||
<div className="p-4 bg-slate-900/50 border border-slate-700 rounded-xl">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<span className="px-3 py-1 bg-green-600/20 text-green-400 text-xs font-bold rounded">
|
||||
POST
|
||||
</span>
|
||||
<code className="text-sm text-white">/api/text-to-image</code>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400">
|
||||
Base URL: <code className="text-purple-400">https://api.banatie.com</code>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Parameters */}
|
||||
<section id="parameters" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Parameters</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
All parameters should be sent in the request body as JSON.
|
||||
</p>
|
||||
|
||||
<Table
|
||||
headers={['Parameter', 'Type', 'Required', 'Description']}
|
||||
rows={parameters.map((param) => [
|
||||
<code key="name" className="text-purple-400">{param.name}</code>,
|
||||
<span key="type" className="text-cyan-400">{param.type}</span>,
|
||||
<span key="required" className={param.required ? 'text-green-400' : 'text-gray-500'}>
|
||||
{param.required ? 'Yes' : 'No'}
|
||||
</span>,
|
||||
param.description,
|
||||
])}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
<TipBox variant="compact" type="info">
|
||||
<strong>Default Values:</strong> If not specified, <code className="px-1.5 py-0.5 bg-slate-800 rounded text-purple-400">filename</code> is
|
||||
auto-generated, <code className="px-1.5 py-0.5 bg-slate-800 rounded text-purple-400">aspectRatio</code> defaults
|
||||
to "1:1", and <code className="px-1.5 py-0.5 bg-slate-800 rounded text-purple-400">autoEnhance</code> is false.
|
||||
</TipBox>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Response */}
|
||||
<section id="response" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Response</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
On success, the API returns a JSON object containing the generated image URL and metadata.
|
||||
</p>
|
||||
|
||||
<CodeBlock
|
||||
code={`{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://cdn.banatie.com/org/project/generated/2025-01/image.png",
|
||||
"filepath": "org/project/generated/2025-01/image.png",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"promptEnhancement": {
|
||||
"enhancedPrompt": "A highly detailed, photorealistic...",
|
||||
"wasEnhanced": true
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"generationTime": 3.42,
|
||||
"model": "imagen-4.0"
|
||||
}
|
||||
}`}
|
||||
language="json"
|
||||
filename="Success Response"
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Error Codes */}
|
||||
<section id="error-codes" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Error Codes</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
The API uses standard HTTP status codes and returns descriptive error messages.
|
||||
</p>
|
||||
|
||||
<Table
|
||||
headers={['Status Code', 'Error Type', 'Description']}
|
||||
rows={[
|
||||
[
|
||||
<code key="code" className="text-red-400">400</code>,
|
||||
'Bad Request',
|
||||
'Missing or invalid parameters in the request body',
|
||||
],
|
||||
[
|
||||
<code key="code" className="text-red-400">401</code>,
|
||||
'Unauthorized',
|
||||
'Missing or invalid API key in X-API-Key header',
|
||||
],
|
||||
[
|
||||
<code key="code" className="text-red-400">429</code>,
|
||||
'Rate Limit',
|
||||
'Too many requests. Check rate limit headers for retry timing',
|
||||
],
|
||||
[
|
||||
<code key="code" className="text-red-400">500</code>,
|
||||
'Server Error',
|
||||
'Internal server error. Contact support if persists',
|
||||
],
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
<TipBox variant="compact" type="warning">
|
||||
<strong>Rate Limits:</strong> Project API keys are limited to 100 requests per hour.
|
||||
Upgrade your plan for higher limits.
|
||||
</TipBox>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Interactive Widget */}
|
||||
<section id="interactive" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Try It Live</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
Test the API directly from this page. Enter your API key and customize the parameters below.
|
||||
</p>
|
||||
|
||||
<InteractiveAPIWidgetFinal
|
||||
endpoint="/api/text-to-image"
|
||||
method="POST"
|
||||
description="Generate an image from a text prompt"
|
||||
parameters={parameters}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Next Steps */}
|
||||
<section id="next-steps" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Next Steps</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<a
|
||||
href="/docs/final/api/upload"
|
||||
className="p-5 bg-gradient-to-br from-purple-500/10 to-transparent border border-slate-700/50 rounded-xl hover:border-purple-500/40 transition-colors group"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-white mb-2 group-hover:text-purple-400 transition-colors">
|
||||
Upload API
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
Learn how to upload reference images for image-to-image generation.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/docs/final/guides/error-handling"
|
||||
className="p-5 bg-gradient-to-br from-cyan-500/10 to-transparent border border-slate-700/50 rounded-xl hover:border-cyan-500/40 transition-colors group"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-white mb-2 group-hover:text-cyan-400 transition-colors">
|
||||
Error Handling
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
Best practices for handling errors and retries in production.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</DocsLayoutFinal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,336 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Authentication Guide - Final Variant
|
||||
*
|
||||
* Features:
|
||||
* - SubsectionNav at top
|
||||
* - Prominent TipBox for security warnings
|
||||
* - Compact TipBox for general tips
|
||||
* - Enhanced tables for rate limits and key types
|
||||
* - Simplified Next Steps
|
||||
*/
|
||||
|
||||
import { DocsLayoutFinal } from '@/components/docs/final/DocsLayoutFinal';
|
||||
import { DocsSidebarFinal } from '@/components/docs/final/DocsSidebarFinal';
|
||||
import { DocsTOCFinal } from '@/components/docs/final/DocsTOCFinal';
|
||||
import { SubsectionNav } from '@/components/shared/SubsectionNav';
|
||||
import { TipBox } from '@/components/docs/shared/TipBox';
|
||||
import { Table } from '@/components/docs/shared/Table';
|
||||
import { Breadcrumb } from '@/components/docs/shared/Breadcrumb';
|
||||
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
|
||||
|
||||
const tocItems = [
|
||||
{ id: 'overview', text: 'Overview', level: 2 },
|
||||
{ id: 'api-keys', text: 'API Keys', level: 2 },
|
||||
{ id: 'key-types', text: 'Key Types', level: 3 },
|
||||
{ id: 'creating-keys', text: 'Creating Keys', level: 3 },
|
||||
{ id: 'using-keys', text: 'Using API Keys', level: 2 },
|
||||
{ id: 'rate-limits', text: 'Rate Limits', level: 2 },
|
||||
{ id: 'security', text: 'Security Best Practices', level: 2 },
|
||||
{ id: 'next-steps', text: 'Next Steps', level: 2 },
|
||||
];
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Documentation', href: '/docs/final' },
|
||||
{ label: 'Demo', href: '/demo' },
|
||||
{ label: 'Examples', href: '/docs/final/examples' },
|
||||
];
|
||||
|
||||
export default function AuthenticationGuidePage() {
|
||||
return (
|
||||
<>
|
||||
{/* Subsection Navigation */}
|
||||
<SubsectionNav
|
||||
items={navItems}
|
||||
currentPath="/docs/final/guides/authentication"
|
||||
ctaText="Join Beta"
|
||||
ctaHref="/signup"
|
||||
/>
|
||||
|
||||
<DocsLayoutFinal
|
||||
sidebar={<DocsSidebarFinal currentPath="/docs/final/guides/authentication" />}
|
||||
toc={<DocsTOCFinal items={tocItems} />}
|
||||
>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{ label: 'Documentation', href: '/docs' },
|
||||
{ label: 'Guides', href: '/docs/final/guides' },
|
||||
{ label: 'Authentication' },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="mb-12">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-white mb-4">Authentication</h1>
|
||||
<p className="text-xl text-gray-400 leading-relaxed">
|
||||
Learn how to authenticate with the Banatie API using API keys, manage rate limits, and
|
||||
implement security best practices.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Overview */}
|
||||
<section id="overview" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Overview</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
Banatie uses API keys to authenticate requests. All API endpoints require authentication
|
||||
via the <code className="px-1.5 py-0.5 bg-slate-800 rounded text-purple-400">X-API-Key</code> header.
|
||||
API keys are tied to organizations and projects, providing fine-grained access control.
|
||||
</p>
|
||||
|
||||
<TipBox variant="compact" type="info">
|
||||
<strong>Quick Start:</strong> New to API authentication? Check out our{' '}
|
||||
<a href="/docs/final" className="text-purple-400 hover:underline">
|
||||
Getting Started guide
|
||||
</a>{' '}
|
||||
for a step-by-step walkthrough.
|
||||
</TipBox>
|
||||
</section>
|
||||
|
||||
{/* API Keys */}
|
||||
<section id="api-keys" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">API Keys</h2>
|
||||
|
||||
<div id="key-types" className="mb-8">
|
||||
<h3 className="text-xl font-semibold text-white mb-4">Key Types</h3>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
Banatie supports two types of API keys, each with different permissions and use cases:
|
||||
</p>
|
||||
|
||||
<Table
|
||||
headers={['Key Type', 'Permissions', 'Expiration', 'Use Case']}
|
||||
rows={[
|
||||
[
|
||||
<code key="type" className="text-purple-400">Master Key</code>,
|
||||
'Full admin access, can create/revoke keys',
|
||||
<span key="exp" className="text-green-400">Never expires</span>,
|
||||
'Server-side admin operations, key management',
|
||||
],
|
||||
[
|
||||
<code key="type" className="text-cyan-400">Project Key</code>,
|
||||
'Image generation only',
|
||||
<span key="exp" className="text-amber-400">90 days</span>,
|
||||
'Application integration, API requests',
|
||||
],
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
<TipBox variant="prominent" type="warning">
|
||||
<strong className="text-amber-300">Master Key Security:</strong> Master keys have full
|
||||
administrative access and never expire. Store them securely in encrypted vaults or
|
||||
secret managers. Never expose master keys in client-side code, logs, or version control.
|
||||
Use project keys for application integration whenever possible.
|
||||
</TipBox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="creating-keys" className="mb-8">
|
||||
<h3 className="text-xl font-semibold text-white mb-4">Creating Keys</h3>
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
For first-time setup, use the bootstrap endpoint to create your initial master key:
|
||||
</p>
|
||||
|
||||
<CodeBlock
|
||||
code={`# Bootstrap your first master key (one-time only)
|
||||
curl -X POST https://api.banatie.com/api/bootstrap/initial-key
|
||||
|
||||
# Response
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"key": "bnt_master_abc123...",
|
||||
"type": "master"
|
||||
}
|
||||
}`}
|
||||
language="bash"
|
||||
filename="Bootstrap Master Key"
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
Once you have a master key, you can create project keys for your applications:
|
||||
</p>
|
||||
|
||||
<CodeBlock
|
||||
code={`# Create a project key with master key authentication
|
||||
curl -X POST https://api.banatie.com/api/admin/keys \\
|
||||
-H "X-API-Key: YOUR_MASTER_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"type": "project",
|
||||
"projectId": "my-app",
|
||||
"name": "Production API Key"
|
||||
}'
|
||||
|
||||
# Response
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"key": "bnt_project_xyz789...",
|
||||
"type": "project",
|
||||
"expiresAt": "2025-04-14T00:00:00Z"
|
||||
}
|
||||
}`}
|
||||
language="bash"
|
||||
filename="Create Project Key"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Using API Keys */}
|
||||
<section id="using-keys" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Using API Keys</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
Include your API key in the <code className="px-1.5 py-0.5 bg-slate-800 rounded text-purple-400">X-API-Key</code> header
|
||||
with every request:
|
||||
</p>
|
||||
|
||||
<CodeBlock
|
||||
code={`# Example: Generate an image with project key
|
||||
curl -X POST https://api.banatie.com/api/text-to-image \\
|
||||
-H "X-API-Key: bnt_project_xyz789..." \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"prompt": "a sunset over the ocean",
|
||||
"aspectRatio": "16:9"
|
||||
}'`}
|
||||
language="bash"
|
||||
filename="Authenticated Request"
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
<TipBox variant="compact" type="info">
|
||||
<strong>Environment Variables:</strong> Store API keys in environment variables, not
|
||||
hardcoded in your application. Example:{' '}
|
||||
<code className="px-1.5 py-0.5 bg-slate-800 rounded text-purple-400">BANATIE_API_KEY</code>
|
||||
</TipBox>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Rate Limits */}
|
||||
<section id="rate-limits" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Rate Limits</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
API keys are subject to rate limits to ensure fair usage and system stability. Limits
|
||||
vary by key type and plan tier:
|
||||
</p>
|
||||
|
||||
<Table
|
||||
headers={['Key Type', 'Rate Limit', 'Reset Window', 'Upgrade Available']}
|
||||
rows={[
|
||||
[
|
||||
<code key="type" className="text-purple-400">Master Key</code>,
|
||||
<span key="limit" className="text-green-400">Unlimited</span>,
|
||||
'N/A',
|
||||
'N/A',
|
||||
],
|
||||
[
|
||||
<code key="type" className="text-cyan-400">Project Key (Free)</code>,
|
||||
<span key="limit" className="text-amber-400">100 requests/hour</span>,
|
||||
'1 hour rolling',
|
||||
<a key="upgrade" href="/pricing" className="text-purple-400 hover:underline">Yes</a>,
|
||||
],
|
||||
[
|
||||
<code key="type" className="text-cyan-400">Project Key (Pro)</code>,
|
||||
<span key="limit" className="text-green-400">1,000 requests/hour</span>,
|
||||
'1 hour rolling',
|
||||
<a key="upgrade" href="/pricing" className="text-purple-400 hover:underline">Yes</a>,
|
||||
],
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
When you exceed rate limits, the API returns a <code className="px-1.5 py-0.5 bg-slate-800 rounded text-red-400">429 Too Many Requests</code> status.
|
||||
Check the response headers for retry timing:
|
||||
</p>
|
||||
|
||||
<CodeBlock
|
||||
code={`# Rate limit response headers
|
||||
X-RateLimit-Limit: 100
|
||||
X-RateLimit-Remaining: 0
|
||||
X-RateLimit-Reset: 1704153600
|
||||
|
||||
# Error response body
|
||||
{
|
||||
"error": "Rate limit exceeded",
|
||||
"retryAfter": 3600
|
||||
}`}
|
||||
language="bash"
|
||||
filename="Rate Limit Response"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Security Best Practices */}
|
||||
<section id="security" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Security Best Practices</h2>
|
||||
|
||||
<TipBox variant="prominent" type="warning">
|
||||
<strong className="text-amber-300">Critical Security Guidelines:</strong>
|
||||
<ul className="mt-3 space-y-2 list-disc list-inside">
|
||||
<li>Never commit API keys to version control systems (Git, SVN, etc.)</li>
|
||||
<li>Store keys in environment variables or secret management services</li>
|
||||
<li>Use project keys in applications, reserve master keys for admin operations</li>
|
||||
<li>Rotate keys regularly, especially after team member changes</li>
|
||||
<li>Implement server-side API calls for production applications</li>
|
||||
<li>Monitor API key usage in your dashboard for suspicious activity</li>
|
||||
</ul>
|
||||
</TipBox>
|
||||
|
||||
<div className="mt-6">
|
||||
<h3 className="text-lg font-semibold text-white mb-3">Key Rotation Example</h3>
|
||||
<CodeBlock
|
||||
code={`# 1. Create new project key
|
||||
curl -X POST https://api.banatie.com/api/admin/keys \\
|
||||
-H "X-API-Key: YOUR_MASTER_KEY" \\
|
||||
-d '{"type": "project", "projectId": "my-app", "name": "New Key"}'
|
||||
|
||||
# 2. Update your application to use new key
|
||||
export BANATIE_API_KEY="bnt_project_new..."
|
||||
|
||||
# 3. Revoke old key
|
||||
curl -X DELETE https://api.banatie.com/api/admin/keys/OLD_KEY_ID \\
|
||||
-H "X-API-Key: YOUR_MASTER_KEY"`}
|
||||
language="bash"
|
||||
filename="Key Rotation Process"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Next Steps */}
|
||||
<section id="next-steps" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Next Steps</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<a
|
||||
href="/docs/final/api/text-to-image"
|
||||
className="p-5 bg-gradient-to-br from-purple-500/10 to-transparent border border-slate-700/50 rounded-xl hover:border-purple-500/40 transition-colors group"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-white mb-2 group-hover:text-purple-400 transition-colors">
|
||||
Start Generating Images
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
Explore the Text to Image API and start building your integration.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/docs/final/guides/error-handling"
|
||||
className="p-5 bg-gradient-to-br from-cyan-500/10 to-transparent border border-slate-700/50 rounded-xl hover:border-cyan-500/40 transition-colors group"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-white mb-2 group-hover:text-cyan-400 transition-colors">
|
||||
Error Handling Guide
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
Learn how to handle authentication errors and implement retry logic.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</DocsLayoutFinal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Getting Started Page - Final Variant
|
||||
*
|
||||
* Production-ready documentation landing page
|
||||
* Features:
|
||||
* - SubsectionNav at top
|
||||
* - Both TipBox styles (compact + prominent)
|
||||
* - Enhanced tables
|
||||
* - Simplified Next Steps (2 cards only)
|
||||
* - Clean, accessible design
|
||||
*/
|
||||
|
||||
import { DocsLayoutFinal } from '@/components/docs/final/DocsLayoutFinal';
|
||||
import { DocsSidebarFinal } from '@/components/docs/final/DocsSidebarFinal';
|
||||
import { DocsTOCFinal } from '@/components/docs/final/DocsTOCFinal';
|
||||
import { SubsectionNav } from '@/components/shared/SubsectionNav';
|
||||
import { TipBox } from '@/components/docs/shared/TipBox';
|
||||
import { Table } from '@/components/docs/shared/Table';
|
||||
import { Breadcrumb } from '@/components/docs/shared/Breadcrumb';
|
||||
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
|
||||
|
||||
const tocItems = [
|
||||
{ id: 'introduction', text: 'Introduction', level: 2 },
|
||||
{ id: 'quick-start', text: 'Quick Start', level: 2 },
|
||||
{ id: 'installation', text: 'Installation', level: 3 },
|
||||
{ id: 'authentication', text: 'Authentication', level: 3 },
|
||||
{ id: 'first-request', text: 'Your First Request', level: 2 },
|
||||
{ id: 'next-steps', text: 'Next Steps', level: 2 },
|
||||
];
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Documentation', href: '/docs/final' },
|
||||
{ label: 'Demo', href: '/demo' },
|
||||
{ label: 'Examples', href: '/docs/final/examples' },
|
||||
];
|
||||
|
||||
export default function GettingStartedPageFinal() {
|
||||
return (
|
||||
<>
|
||||
{/* Subsection Navigation */}
|
||||
<SubsectionNav
|
||||
items={navItems}
|
||||
currentPath="/docs/final"
|
||||
ctaText="Join Beta"
|
||||
ctaHref="/signup"
|
||||
/>
|
||||
|
||||
<DocsLayoutFinal
|
||||
sidebar={<DocsSidebarFinal currentPath="/docs/final" />}
|
||||
toc={<DocsTOCFinal items={tocItems} />}
|
||||
>
|
||||
<Breadcrumb items={[{ label: 'Documentation', href: '/docs' }, { label: 'Getting Started' }]} />
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="mb-12">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-white mb-4">Getting Started</h1>
|
||||
<p className="text-xl text-gray-400 leading-relaxed">
|
||||
Welcome to the Banatie API documentation. Learn how to integrate AI-powered image
|
||||
generation into your applications in minutes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Introduction */}
|
||||
<section id="introduction" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Introduction</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
Banatie is a developer-first API for AI-powered image generation. Built on Google Gemini
|
||||
2.5 Flash and Imagen 4.0, it transforms text prompts and reference images into
|
||||
production-ready visuals.
|
||||
</p>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
Whether you are building a content creation platform, e-commerce site, or creative tool,
|
||||
Banatie provides the infrastructure you need to generate high-quality images at scale.
|
||||
</p>
|
||||
|
||||
{/* Compact Tip Box */}
|
||||
<TipBox variant="compact" type="info">
|
||||
<strong>New to API integration?</strong> Start with our{' '}
|
||||
<a href="/docs/final/examples" className="text-purple-400 hover:underline">
|
||||
code examples
|
||||
</a>{' '}
|
||||
to see common use cases in action.
|
||||
</TipBox>
|
||||
</section>
|
||||
|
||||
{/* Quick Start */}
|
||||
<section id="quick-start" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Quick Start</h2>
|
||||
|
||||
<div id="installation" className="mb-8">
|
||||
<h3 className="text-xl font-semibold text-white mb-3">Installation</h3>
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
Banatie is a REST API, so you do not need to install any libraries. However, we provide
|
||||
SDKs for popular languages to make integration easier.
|
||||
</p>
|
||||
|
||||
<CodeBlock
|
||||
code={`# Using npm (JavaScript/TypeScript)
|
||||
npm install @banatie/sdk
|
||||
|
||||
# Using pip (Python)
|
||||
pip install banatie
|
||||
|
||||
# Using Go
|
||||
go get github.com/banatie/sdk-go`}
|
||||
language="bash"
|
||||
filename="Installation"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="authentication" className="mb-8">
|
||||
<h3 className="text-xl font-semibold text-white mb-3">Authentication</h3>
|
||||
<p className="text-gray-300 leading-relaxed mb-4">
|
||||
All API requests require an API key. You can create an API key from your dashboard or
|
||||
using the bootstrap endpoint for initial setup.
|
||||
</p>
|
||||
|
||||
{/* Prominent Tip Box for Security Warning */}
|
||||
<div className="mb-6">
|
||||
<TipBox variant="prominent" type="warning">
|
||||
<strong className="text-amber-300">Security Best Practice:</strong> Keep your API keys secure.
|
||||
Never commit them to public repositories or expose them in client-side code. Use environment
|
||||
variables and server-side implementations for production applications.
|
||||
</TipBox>
|
||||
</div>
|
||||
|
||||
<CodeBlock
|
||||
code={`# Create your first API key (one-time bootstrap)
|
||||
curl -X POST https://api.banatie.com/api/bootstrap/initial-key
|
||||
|
||||
# Save the returned key securely
|
||||
export BANATIE_API_KEY="bnt_your_key_here"`}
|
||||
language="bash"
|
||||
filename="Get API Key"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* First Request */}
|
||||
<section id="first-request" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Your First Request</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
Let's generate your first image! This example uses curl, but you can use any HTTP client
|
||||
or our SDKs.
|
||||
</p>
|
||||
|
||||
<CodeBlock
|
||||
code={`curl -X POST https://api.banatie.com/api/text-to-image \\
|
||||
-H "X-API-Key: YOUR_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"prompt": "a serene mountain landscape at sunset",
|
||||
"filename": "mountain_sunset",
|
||||
"aspectRatio": "16:9",
|
||||
"autoEnhance": true
|
||||
}'`}
|
||||
language="bash"
|
||||
filename="Generate Image"
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
<p className="text-sm font-semibold text-gray-300 mb-3">Expected Response:</p>
|
||||
<div className="relative p-4 bg-green-500/5 border border-green-500/30 rounded-xl shadow-lg shadow-green-500/10">
|
||||
<div className="absolute top-3 right-3">
|
||||
<span className="px-2 py-1 text-xs bg-green-500/20 text-green-400 rounded-full">
|
||||
✓ 200 Success
|
||||
</span>
|
||||
</div>
|
||||
<pre className="text-xs text-gray-300 overflow-x-auto mt-6">
|
||||
<code>{`{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://cdn.banatie.com/org/project/generated/2025-01/mountain_sunset.png",
|
||||
"filepath": "org/project/generated/2025-01/mountain_sunset.png",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"promptEnhancement": {
|
||||
"enhancedPrompt": "A breathtaking mountain landscape..."
|
||||
}
|
||||
}
|
||||
}`}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Next Steps - Simplified to 2 Cards */}
|
||||
<section id="next-steps" className="mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Next Steps</h2>
|
||||
<p className="text-gray-300 leading-relaxed mb-6">
|
||||
Now that you have generated your first image, explore these resources to build more advanced integrations:
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<a
|
||||
href="/docs/final/api/text-to-image"
|
||||
className="p-5 bg-gradient-to-br from-purple-500/10 to-transparent border border-slate-700/50 rounded-xl hover:border-purple-500/40 transition-colors group"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-white mb-2 group-hover:text-purple-400 transition-colors">
|
||||
API Reference
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
Explore all available endpoints, parameters, and response formats.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/docs/final/guides/authentication"
|
||||
className="p-5 bg-gradient-to-br from-cyan-500/10 to-transparent border border-slate-700/50 rounded-xl hover:border-cyan-500/40 transition-colors group"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-white mb-2 group-hover:text-cyan-400 transition-colors">
|
||||
Authentication Guide
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
Learn about API keys, rate limits, and security best practices.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</DocsLayoutFinal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -53,113 +53,6 @@ export default function DocsIndexPage() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{/* Final Variant - Recommended */}
|
||||
<div className="mb-12">
|
||||
<div className="text-center mb-6">
|
||||
<span className="inline-block px-4 py-1.5 rounded-full bg-gradient-to-r from-purple-500 to-cyan-500 text-white text-sm font-semibold mb-2">
|
||||
⭐ RECOMMENDED
|
||||
</span>
|
||||
<h2 className="text-2xl font-bold text-white">Production-Ready Design</h2>
|
||||
</div>
|
||||
<a
|
||||
href="/docs/final"
|
||||
className="group block p-10 bg-gradient-to-br from-purple-500/20 via-cyan-500/20 to-purple-500/20 border-4 border-purple-500/50 rounded-3xl hover:border-purple-500/80 transition-all duration-300 shadow-2xl hover:shadow-purple-500/40 hover:scale-[1.02]"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row items-center gap-8">
|
||||
<div className="flex-shrink-0 text-6xl">✨</div>
|
||||
<div className="flex-1 text-center md:text-left">
|
||||
<h3 className="text-3xl font-bold text-white mb-3 group-hover:text-purple-300 transition-colors">
|
||||
Final Variant
|
||||
</h3>
|
||||
<h4 className="text-xl font-semibold text-purple-300 mb-4">
|
||||
Best of All Worlds - Clean, Enhanced & Production-Ready
|
||||
</h4>
|
||||
<p className="text-base text-gray-300 mb-6 leading-relaxed max-w-3xl">
|
||||
The perfect combination: Variant A's clean aesthetic enhanced with expandable code blocks, success/error response styling, clickable image URLs, two tip box styles, enhanced tables, and simplified navigation. This is the variant we recommend for production use.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-4 mb-6">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<svg className="w-5 h-5 text-purple-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Clean Variant A base</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<svg className="w-5 h-5 text-cyan-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Expandable code blocks</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<svg className="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Success/error styling</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<svg className="w-5 h-5 text-purple-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Two tip box styles</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<svg className="w-5 h-5 text-cyan-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Enhanced tables</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<svg className="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Reusable navigation</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center md:justify-start gap-3 text-purple-300 text-lg font-bold group-hover:gap-4 transition-all">
|
||||
<span>Explore Final Variant</span>
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Other Variants for Reference */}
|
||||
<div className="text-center mb-8">
|
||||
<h3 className="text-xl font-semibold text-gray-400 mb-2">Other Variants (Reference)</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
Explore the original variants that inspired the final design
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Variant Cards */}
|
||||
<div className="grid md:grid-cols-3 gap-8 mb-16">
|
||||
{/* Variant A: Clean & Minimal */}
|
||||
|
|
|
|||
|
|
@ -62,40 +62,3 @@ body {
|
|||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Custom Scrollbars for Dark Theme */
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgb(71, 85, 105) rgb(15, 23, 42); /* slate-600 on slate-950 */
|
||||
}
|
||||
|
||||
/* Webkit browsers (Chrome, Safari, Edge) */
|
||||
*::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: rgb(15, 23, 42); /* slate-950 */
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: rgb(71, 85, 105); /* slate-600 */
|
||||
border-radius: 4px;
|
||||
border: 2px solid rgb(15, 23, 42); /* slate-950 */
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(100, 116, 139); /* slate-500 */
|
||||
}
|
||||
|
||||
/* Code blocks - slightly brighter scrollbar for better visibility */
|
||||
pre::-webkit-scrollbar-thumb {
|
||||
background: rgb(100, 116, 139); /* slate-500 */
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(148, 163, 184); /* slate-400 */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Documentation Layout - Final Variant
|
||||
*
|
||||
* Based on Variant A: Clean & Minimal with enhancements
|
||||
* This is the production-ready version combining the best aspects
|
||||
*
|
||||
* Layout Structure:
|
||||
* - Left Sidebar: Thin (256px) with minimal design
|
||||
* - Content Area: Wide margins (max-w-3xl) for comfortable reading
|
||||
* - Right TOC: Subtle and unobtrusive (224px)
|
||||
*
|
||||
* Responsive Behavior:
|
||||
* - Mobile (<768px): Single column, sidebars become overlays
|
||||
* - Tablet (768px-1024px): Content + TOC only
|
||||
* - Desktop (>1024px): Full three-column layout
|
||||
*
|
||||
* Design Characteristics:
|
||||
* - Generous whitespace
|
||||
* - Subtle borders and shadows
|
||||
* - Focus on content clarity
|
||||
* - Minimal visual noise
|
||||
* - Accessibility compliant (WCAG 2.1 AA)
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface DocsLayoutFinalProps {
|
||||
sidebar: ReactNode;
|
||||
children: ReactNode;
|
||||
toc: ReactNode;
|
||||
}
|
||||
|
||||
export const DocsLayoutFinal = ({ sidebar, children, toc }: DocsLayoutFinalProps) => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950">
|
||||
{/* Animated gradient background (matching landing page) */}
|
||||
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute top-1/4 -left-1/4 w-96 h-96 bg-purple-600/10 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div className="absolute bottom-1/4 -right-1/4 w-96 h-96 bg-cyan-600/10 rounded-full blur-3xl animate-pulse delay-700"></div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex">
|
||||
{/* Left Sidebar - Thin, minimal design */}
|
||||
<aside className="hidden lg:block w-64 border-r border-white/10 bg-slate-950/50 backdrop-blur-sm sticky top-0 h-screen overflow-y-auto">
|
||||
{sidebar}
|
||||
</aside>
|
||||
|
||||
{/* Main Content Area - Wide margins for comfortable reading */}
|
||||
<main className="flex-1 min-w-0">
|
||||
<div className="max-w-3xl mx-auto px-6 lg:px-12 py-12">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Right TOC - Subtle and unobtrusive */}
|
||||
<aside className="hidden xl:block w-56 border-l border-white/10 bg-slate-950/30 backdrop-blur-sm sticky top-0 h-screen overflow-y-auto">
|
||||
{toc}
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Documentation Sidebar - Final Variant
|
||||
*
|
||||
* Based on Variant A with FIXED active states
|
||||
*
|
||||
* Key Improvements:
|
||||
* - Consistent active state styling for both parent and child items
|
||||
* - Clean left border indicator (no background boxes)
|
||||
* - Parent active: purple left border + white text
|
||||
* - Child active: purple left border + white text (NO background)
|
||||
*
|
||||
* Features:
|
||||
* - Thin sidebar with subtle hover states
|
||||
* - Collapsible section groups
|
||||
* - Minimal icons (chevron for expandable items)
|
||||
* - Clean, uncluttered appearance
|
||||
*
|
||||
* Navigation Structure:
|
||||
* - Getting Started
|
||||
* - API Reference (collapsible)
|
||||
* - Guides (collapsible)
|
||||
* - Examples
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
interface NavItem {
|
||||
label: string;
|
||||
href: string;
|
||||
icon?: string;
|
||||
children?: NavItem[];
|
||||
}
|
||||
|
||||
interface DocsSidebarFinalProps {
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
const navigationItems: NavItem[] = [
|
||||
{
|
||||
label: 'Getting Started',
|
||||
href: '/docs/final',
|
||||
icon: '🚀',
|
||||
},
|
||||
{
|
||||
label: 'API Reference',
|
||||
href: '/docs/final/api',
|
||||
icon: '📚',
|
||||
children: [
|
||||
{ label: 'Text to Image', href: '/docs/final/api/text-to-image' },
|
||||
{ label: 'Upload', href: '/docs/final/api/upload' },
|
||||
{ label: 'Images', href: '/docs/final/api/images' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Guides',
|
||||
href: '/docs/final/guides',
|
||||
icon: '📖',
|
||||
children: [
|
||||
{ label: 'Authentication', href: '/docs/final/guides/authentication' },
|
||||
{ label: 'Error Handling', href: '/docs/final/guides/error-handling' },
|
||||
{ label: 'Rate Limits', href: '/docs/final/guides/rate-limits' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Examples',
|
||||
href: '/docs/final/examples',
|
||||
icon: '💡',
|
||||
},
|
||||
];
|
||||
|
||||
export const DocsSidebarFinal = ({ currentPath }: DocsSidebarFinalProps) => {
|
||||
const [expandedSections, setExpandedSections] = useState<string[]>(['API Reference', 'Guides']);
|
||||
|
||||
const toggleSection = (label: string) => {
|
||||
setExpandedSections((prev) =>
|
||||
prev.includes(label) ? prev.filter((item) => item !== label) : [...prev, label]
|
||||
);
|
||||
};
|
||||
|
||||
const isActive = (href: string) => currentPath === href;
|
||||
const isExpanded = (label: string) => expandedSections.includes(label);
|
||||
|
||||
return (
|
||||
<nav className="p-6" aria-label="Documentation navigation">
|
||||
{/* Logo/Title */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-lg font-semibold text-white">Documentation</h2>
|
||||
<p className="text-xs text-gray-500 mt-1">Final: Production</p>
|
||||
</div>
|
||||
|
||||
{/* Navigation Items */}
|
||||
<ul className="space-y-1">
|
||||
{navigationItems.map((item) => {
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
const expanded = isExpanded(item.label);
|
||||
const active = isActive(item.href);
|
||||
|
||||
return (
|
||||
<li key={item.label}>
|
||||
{/* Parent Item */}
|
||||
<div className="relative">
|
||||
{active && (
|
||||
<div className="absolute left-0 top-0 bottom-0 w-0.5 bg-purple-500 rounded-r"></div>
|
||||
)}
|
||||
<a
|
||||
href={item.href}
|
||||
onClick={
|
||||
hasChildren
|
||||
? (e) => {
|
||||
e.preventDefault();
|
||||
toggleSection(item.label);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={`
|
||||
flex items-center justify-between px-3 py-2 rounded-lg text-sm transition-colors
|
||||
${active ? 'bg-purple-500/10 text-white font-medium' : 'text-gray-400 hover:text-white hover:bg-white/5'}
|
||||
`}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{item.icon && <span className="text-base">{item.icon}</span>}
|
||||
<span>{item.label}</span>
|
||||
</span>
|
||||
{hasChildren && (
|
||||
<svg
|
||||
className={`w-4 h-4 transition-transform ${expanded ? 'rotate-90' : ''}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Children Items - FIXED ACTIVE STATE */}
|
||||
{hasChildren && expanded && (
|
||||
<ul className="ml-6 mt-1 space-y-1 border-l border-slate-800">
|
||||
{item.children!.map((child) => {
|
||||
const childActive = isActive(child.href);
|
||||
return (
|
||||
<li key={child.label}>
|
||||
<a
|
||||
href={child.href}
|
||||
className={`
|
||||
block px-3 py-1.5 text-sm transition-colors ml-[-2px]
|
||||
${
|
||||
childActive
|
||||
? 'text-white font-medium border-l-2 border-purple-500 bg-transparent pl-4'
|
||||
: 'text-gray-500 hover:text-gray-300 border-l-2 border-transparent pl-4'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{child.label}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
||||
{/* Bottom Links */}
|
||||
<div className="mt-12 pt-6 border-t border-slate-800">
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li>
|
||||
<a href="/docs" className="text-gray-500 hover:text-gray-300 transition-colors">
|
||||
← Back to variants
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Table of Contents - Final Variant
|
||||
*
|
||||
* Based on Variant A: Clean & Minimal (no changes needed)
|
||||
*
|
||||
* Features:
|
||||
* - Small indicator dots for section levels
|
||||
* - Smooth scroll to section
|
||||
* - Active section highlighting (purple text)
|
||||
* - Sticky positioning
|
||||
* - Minimal visual weight
|
||||
*
|
||||
* Behavior:
|
||||
* - Extracts H2 and H3 headings from content
|
||||
* - Tracks scroll position to highlight active section
|
||||
* - Click to smooth scroll to section
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface TocItem {
|
||||
id: string;
|
||||
text: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
interface DocsTOCFinalProps {
|
||||
items: TocItem[];
|
||||
}
|
||||
|
||||
export const DocsTOCFinal = ({ items }: DocsTOCFinalProps) => {
|
||||
const [activeId, setActiveId] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setActiveId(entry.target.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: '-20% 0px -35% 0px' }
|
||||
);
|
||||
|
||||
items.forEach((item) => {
|
||||
const element = document.getElementById(item.id);
|
||||
if (element) observer.observe(element);
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [items]);
|
||||
|
||||
const scrollToSection = (id: string) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
if (items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="p-6 sticky top-6" aria-label="Table of contents">
|
||||
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-4">
|
||||
On This Page
|
||||
</h3>
|
||||
|
||||
<ul className="space-y-2.5 text-sm">
|
||||
{items.map((item) => {
|
||||
const isActive = activeId === item.id;
|
||||
const isH3 = item.level === 3;
|
||||
|
||||
return (
|
||||
<li key={item.id} className={isH3 ? 'ml-4' : ''}>
|
||||
<button
|
||||
onClick={() => scrollToSection(item.id)}
|
||||
className={`
|
||||
flex items-start gap-2 text-left w-full transition-colors group
|
||||
${isActive ? 'text-purple-400' : 'text-gray-500 hover:text-gray-300'}
|
||||
`}
|
||||
>
|
||||
{/* Indicator dot */}
|
||||
<span
|
||||
className={`
|
||||
flex-shrink-0 w-1 h-1 rounded-full mt-2 transition-colors
|
||||
${isActive ? 'bg-purple-400' : 'bg-gray-600 group-hover:bg-gray-400'}
|
||||
`}
|
||||
></span>
|
||||
<span className="line-clamp-2">{item.text}</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,453 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Interactive API Widget - Final Variant
|
||||
*
|
||||
* Enhanced version of Variant A with:
|
||||
* 1. Expand button for full-screen code view
|
||||
* 2. Success response styling (green accent)
|
||||
* 3. Error response styling (red accent)
|
||||
* 4. Clickable image URLs in response
|
||||
* 5. Status badges (200 Success, Error)
|
||||
*
|
||||
* Features:
|
||||
* - Multi-language code tabs (curl, JavaScript, Python, Go)
|
||||
* - API key input field (persists via localStorage)
|
||||
* - "Try It" button to execute live requests
|
||||
* - Response viewer with enhanced styling
|
||||
* - Clean, focused design
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { CodeBlockExpanded } from '@/components/docs/shared/CodeBlockExpanded';
|
||||
|
||||
interface InteractiveAPIWidgetFinalProps {
|
||||
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';
|
||||
|
||||
export const InteractiveAPIWidgetFinal = ({
|
||||
endpoint,
|
||||
method,
|
||||
description,
|
||||
parameters = [],
|
||||
}: InteractiveAPIWidgetFinalProps) => {
|
||||
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 [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
// Parameter values
|
||||
const [paramValues, setParamValues] = useState<Record<string, string>>({});
|
||||
|
||||
// Load API key from localStorage
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(API_KEY_STORAGE);
|
||||
if (stored) setApiKey(stored);
|
||||
|
||||
// Initialize with proper defaults
|
||||
const defaults: Record<string, string> = {
|
||||
prompt: 'a futuristic city at sunset',
|
||||
filename: 'demo',
|
||||
aspectRatio: '16:9',
|
||||
};
|
||||
|
||||
// Override with parameter defaults if provided
|
||||
parameters.forEach((param) => {
|
||||
if (param.defaultValue) {
|
||||
defaults[param.name] = param.defaultValue;
|
||||
}
|
||||
});
|
||||
|
||||
setParamValues(defaults);
|
||||
}, [parameters]);
|
||||
|
||||
// Save API key to localStorage
|
||||
const handleApiKeyChange = (value: string) => {
|
||||
setApiKey(value);
|
||||
if (value) {
|
||||
localStorage.setItem(API_KEY_STORAGE, value);
|
||||
} else {
|
||||
localStorage.removeItem(API_KEY_STORAGE);
|
||||
}
|
||||
};
|
||||
|
||||
// Generate code examples
|
||||
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();
|
||||
console.log(data);`;
|
||||
|
||||
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)
|
||||
print(response.json())`;
|
||||
|
||||
case 'go':
|
||||
return `package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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)
|
||||
defer resp.Body.Close()
|
||||
}`;
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// Generate all language codes at once
|
||||
const generateAllCodes = (): Record<Language, string> => {
|
||||
const url = `${API_BASE_URL}${endpoint}`;
|
||||
|
||||
return {
|
||||
curl: `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"
|
||||
}'`,
|
||||
|
||||
javascript: `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();
|
||||
console.log(data);`,
|
||||
|
||||
python: `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)
|
||||
print(response.json())`,
|
||||
|
||||
go: `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)
|
||||
defer resp.Body.Close()
|
||||
}`,
|
||||
};
|
||||
};
|
||||
|
||||
// Execute API request
|
||||
const executeRequest = async () => {
|
||||
if (!apiKey) {
|
||||
setError('Please enter your API key');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsExecuting(true);
|
||||
setError(null);
|
||||
setResponse(null);
|
||||
|
||||
try {
|
||||
// Build proper request body
|
||||
const body = {
|
||||
prompt: paramValues.prompt || 'a futuristic city at sunset',
|
||||
filename: paramValues.filename || 'demo',
|
||||
aspectRatio: paramValues.aspectRatio || '16:9',
|
||||
};
|
||||
|
||||
console.log('🔵 API Request:', {
|
||||
url: `${API_BASE_URL}${endpoint}`,
|
||||
method,
|
||||
headers: { 'X-API-Key': apiKey.substring(0, 10) + '...' },
|
||||
body,
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
console.log('🟢 Response Status:', res.status, res.statusText);
|
||||
|
||||
// Handle non-OK responses
|
||||
if (!res.ok) {
|
||||
let errorMessage = `HTTP ${res.status}: ${res.statusText}`;
|
||||
try {
|
||||
const errorData = await res.json();
|
||||
errorMessage = errorData.message || errorData.error || errorMessage;
|
||||
} catch {
|
||||
// Response is not JSON
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
console.log('✅ Response Data:', data);
|
||||
setResponse(data);
|
||||
} catch (err) {
|
||||
console.error('❌ API Error:', err);
|
||||
const errorMsg = err instanceof Error ? err.message : 'Failed to execute request';
|
||||
setError(`Request failed: ${errorMsg}`);
|
||||
} finally {
|
||||
setIsExecuting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Copy code to clipboard
|
||||
const copyCode = () => {
|
||||
navigator.clipboard.writeText(generateCode());
|
||||
};
|
||||
|
||||
// Render response with clickable URLs
|
||||
const renderResponse = (data: any): string => {
|
||||
return JSON.stringify(data, null, 2);
|
||||
};
|
||||
|
||||
// Check if response is success
|
||||
const isSuccess = response && response.success === true;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="my-8 bg-slate-900/50 backdrop-blur-sm border border-slate-700 rounded-2xl overflow-hidden">
|
||||
{/* Header with API Key Input */}
|
||||
<div className="p-4 border-b border-slate-700 bg-slate-900/80">
|
||||
<div className="flex flex-col md:flex-row gap-3 items-start md:items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white mb-1">Try it out</h3>
|
||||
<p className="text-xs text-gray-500">{description}</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 w-full md:w-64">
|
||||
<input
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={(e) => handleApiKeyChange(e.target.value)}
|
||||
placeholder="Enter your API key"
|
||||
className="w-full px-3 py-2 text-xs bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Language Tabs with Expand Button */}
|
||||
<div className="flex items-center justify-between bg-slate-950/50 px-4 py-2 border-b border-slate-700">
|
||||
<div className="flex gap-2">
|
||||
{(['curl', 'javascript', 'python', 'go'] as Language[]).map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
onClick={() => setLanguage(lang)}
|
||||
className={`px-3 py-1 text-xs rounded transition-colors ${
|
||||
language === lang
|
||||
? 'bg-slate-700 text-white'
|
||||
: 'text-gray-400 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{lang === 'javascript' ? 'JavaScript' : lang.charAt(0).toUpperCase() + lang.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setIsExpanded(true)}
|
||||
className="px-3 py-1 text-xs text-gray-400 hover:text-white transition-colors"
|
||||
aria-label="Expand code view"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={copyCode}
|
||||
className="px-3 py-1 text-xs bg-purple-600/20 hover:bg-purple-600/30 text-purple-400 rounded transition-colors"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Code Display */}
|
||||
<div className="p-4 bg-slate-950/50">
|
||||
<pre className="text-xs text-gray-300 overflow-x-auto">
|
||||
<code>{generateCode()}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Try It Button */}
|
||||
<div className="p-4 border-t border-slate-700 bg-slate-900/50">
|
||||
<button
|
||||
onClick={executeRequest}
|
||||
disabled={!apiKey || isExecuting}
|
||||
className="w-full px-4 py-2.5 rounded-lg bg-gradient-to-r from-purple-600 to-cyan-600 text-white text-sm font-semibold hover:from-purple-500 hover:to-cyan-500 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isExecuting ? 'Executing...' : 'Try It'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Response Section - Enhanced with success/error styling */}
|
||||
{(response || error) && (
|
||||
<div className="border-t border-slate-700">
|
||||
<div className="p-4 bg-slate-900/80">
|
||||
<h4 className="text-sm font-semibold text-white mb-3">Response</h4>
|
||||
{error ? (
|
||||
// Error Response
|
||||
<div className="relative p-4 bg-red-500/5 border border-red-500/30 rounded-xl">
|
||||
<div className="absolute top-3 right-3">
|
||||
<span className="px-2 py-1 text-xs bg-red-500/20 text-red-400 rounded-full">
|
||||
✗ Error
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-red-400 mt-6">{error}</p>
|
||||
</div>
|
||||
) : isSuccess ? (
|
||||
// Success Response
|
||||
<div className="relative p-4 bg-green-500/5 border border-green-500/30 rounded-xl shadow-lg shadow-green-500/10">
|
||||
<div className="absolute top-3 right-3">
|
||||
<span className="px-2 py-1 text-xs bg-green-500/20 text-green-400 rounded-full">
|
||||
✓ 200 Success
|
||||
</span>
|
||||
</div>
|
||||
<pre className="text-xs text-gray-300 overflow-x-auto mt-6">
|
||||
<code>{renderResponse(response)}</code>
|
||||
</pre>
|
||||
</div>
|
||||
) : (
|
||||
// Normal Response
|
||||
<pre className="p-3 bg-slate-950/50 rounded-lg text-xs text-gray-300 overflow-x-auto">
|
||||
<code>{renderResponse(response)}</code>
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Expanded Code Modal */}
|
||||
<CodeBlockExpanded
|
||||
isOpen={isExpanded}
|
||||
onClose={() => setIsExpanded(false)}
|
||||
codes={generateAllCodes()}
|
||||
initialLanguage={language}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Code Block Expanded Modal Component
|
||||
*
|
||||
* Full-screen code viewer for better readability
|
||||
* Features:
|
||||
* - Full-screen overlay with dark backdrop
|
||||
* - Centered modal with max-width constraint
|
||||
* - Language tabs for switching between languages
|
||||
* - Close button (X) in top-right
|
||||
* - Escape key support
|
||||
* - Larger code text in expanded mode
|
||||
* - Copy button with feedback
|
||||
* - Language badge
|
||||
*
|
||||
* Usage:
|
||||
* <CodeBlockExpanded
|
||||
* isOpen={isOpen}
|
||||
* onClose={() => setIsOpen(false)}
|
||||
* codes={{ curl: '...', javascript: '...', python: '...', go: '...' }}
|
||||
* initialLanguage="curl"
|
||||
* filename="example.js"
|
||||
* />
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
type Language = 'curl' | 'javascript' | 'python' | 'go';
|
||||
|
||||
interface CodeBlockExpandedProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
codes: Record<Language, string>;
|
||||
initialLanguage: Language;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
export const CodeBlockExpanded = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
codes,
|
||||
initialLanguage,
|
||||
filename,
|
||||
}: CodeBlockExpandedProps) => {
|
||||
const [currentLanguage, setCurrentLanguage] = useState<Language>(initialLanguage);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// Update language when modal opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setCurrentLanguage(initialLanguage);
|
||||
}
|
||||
}, [isOpen, initialLanguage]);
|
||||
|
||||
// Handle escape key
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
// Prevent body scroll when modal is open
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleEscape);
|
||||
document.body.style.overflow = 'unset';
|
||||
};
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
// Copy to clipboard
|
||||
const copyCode = () => {
|
||||
navigator.clipboard.writeText(codes[currentLanguage]);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Expanded code view"
|
||||
>
|
||||
{/* Modal Content */}
|
||||
<div
|
||||
className="relative w-full max-w-7xl max-h-[90vh] overflow-hidden rounded-2xl bg-slate-950 border border-slate-700 shadow-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-700 bg-slate-900/80">
|
||||
{/* Left: Language Tabs */}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-gray-400 mr-2">Language:</span>
|
||||
<div className="flex gap-2">
|
||||
{(['curl', 'javascript', 'python', 'go'] as Language[]).map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
onClick={() => setCurrentLanguage(lang)}
|
||||
className={`px-3 py-1.5 text-xs rounded-lg transition-colors ${
|
||||
currentLanguage === lang
|
||||
? 'bg-purple-500/20 text-purple-300 font-semibold'
|
||||
: 'text-gray-400 hover:text-white hover:bg-slate-800'
|
||||
}`}
|
||||
>
|
||||
{lang === 'javascript' ? 'JavaScript' : lang.charAt(0).toUpperCase() + lang.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Copy Button */}
|
||||
<button
|
||||
onClick={copyCode}
|
||||
className={`px-4 py-1.5 text-xs rounded-lg transition-colors ${
|
||||
copied
|
||||
? 'bg-green-500/20 text-green-400'
|
||||
: 'bg-purple-600/20 hover:bg-purple-600/30 text-purple-400'
|
||||
}`}
|
||||
>
|
||||
{copied ? '✓ Copied!' : 'Copy'}
|
||||
</button>
|
||||
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 text-gray-400 hover:text-white transition-colors rounded-lg hover:bg-slate-800"
|
||||
aria-label="Close expanded view"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Code Content - Larger text, better spacing */}
|
||||
<div className="overflow-auto max-h-[calc(90vh-6rem)] p-8 bg-slate-950/50">
|
||||
<pre className="text-base text-gray-300 leading-relaxed">
|
||||
<code>{codes[currentLanguage]}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Hint Text */}
|
||||
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 px-3 py-1.5 bg-slate-900/90 border border-slate-700 rounded-full">
|
||||
<p className="text-xs text-gray-400">
|
||||
Press <kbd className="px-1.5 py-0.5 bg-slate-800 rounded text-purple-400">Esc</kbd> to close
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Enhanced Table Component
|
||||
*
|
||||
* Features:
|
||||
* - Larger font: text-base for cells (more readable)
|
||||
* - Better padding: py-4 px-6 (spacious)
|
||||
* - Full-width with proper column distribution
|
||||
* - Hover states for rows
|
||||
* - Responsive: horizontal scroll on mobile
|
||||
* - Optional sortable headers (UI only, no logic yet)
|
||||
*
|
||||
* Usage:
|
||||
* <Table
|
||||
* headers={['Name', 'Type', 'Required', 'Description']}
|
||||
* rows={[
|
||||
* ['prompt', 'string', 'Yes', 'The text prompt'],
|
||||
* ['filename', 'string', 'No', 'Output filename'],
|
||||
* ]}
|
||||
* />
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface TableProps {
|
||||
headers: string[];
|
||||
rows: (string | ReactNode)[][];
|
||||
sortable?: boolean;
|
||||
}
|
||||
|
||||
export const Table = ({ headers, rows, sortable = false }: TableProps) => {
|
||||
return (
|
||||
<div className="overflow-x-auto rounded-xl border border-slate-700 bg-slate-900/50 backdrop-blur-sm">
|
||||
<table className="w-full">
|
||||
{/* Header */}
|
||||
<thead>
|
||||
<tr className="border-b border-slate-700 bg-slate-800/50">
|
||||
{headers.map((header, idx) => (
|
||||
<th
|
||||
key={idx}
|
||||
className="py-4 px-6 text-left text-sm font-semibold text-white whitespace-nowrap"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{header}
|
||||
{sortable && (
|
||||
<button
|
||||
className="text-gray-500 hover:text-gray-300 transition-colors"
|
||||
aria-label={`Sort by ${header}`}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* Body */}
|
||||
<tbody>
|
||||
{rows.map((row, rowIdx) => (
|
||||
<tr
|
||||
key={rowIdx}
|
||||
className="border-b border-slate-800 last:border-b-0 hover:bg-slate-800/30 transition-colors"
|
||||
>
|
||||
{row.map((cell, cellIdx) => (
|
||||
<td
|
||||
key={cellIdx}
|
||||
className="py-4 px-6 text-base text-gray-300 align-top"
|
||||
>
|
||||
{cell}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* TipBox Component - Dual Style System
|
||||
*
|
||||
* Provides two distinct styles for callouts/notes:
|
||||
*
|
||||
* 1. Compact Style (Variant A inspired):
|
||||
* - Small emoji icon on left
|
||||
* - Compact padding (p-4)
|
||||
* - Smaller text (text-sm)
|
||||
* - Subtle background with thin border
|
||||
* - Best for minor notes and tips
|
||||
*
|
||||
* 2. Prominent Style (Adapted from Variant C):
|
||||
* - NO icon
|
||||
* - Larger padding (p-6)
|
||||
* - Larger text (text-base)
|
||||
* - Gradient border with soft glow
|
||||
* - More visual weight
|
||||
* - Best for important warnings and security notices
|
||||
*
|
||||
* Usage:
|
||||
* <TipBox variant="compact" type="info">
|
||||
* This is a compact tip
|
||||
* </TipBox>
|
||||
*
|
||||
* <TipBox variant="prominent" type="warning">
|
||||
* This is an important security warning
|
||||
* </TipBox>
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type TipType = 'info' | 'warning' | 'success';
|
||||
type TipVariant = 'compact' | 'prominent';
|
||||
|
||||
interface TipBoxProps {
|
||||
children: ReactNode;
|
||||
variant?: TipVariant;
|
||||
type?: TipType;
|
||||
}
|
||||
|
||||
const icons: Record<TipType, string> = {
|
||||
info: '💡',
|
||||
warning: '⚠️',
|
||||
success: '✓',
|
||||
};
|
||||
|
||||
const compactStyles: Record<TipType, string> = {
|
||||
info: 'bg-purple-500/10 border-purple-500/20 text-purple-300',
|
||||
warning: 'bg-amber-500/10 border-amber-500/20 text-amber-300',
|
||||
success: 'bg-green-500/10 border-green-500/20 text-green-300',
|
||||
};
|
||||
|
||||
const prominentStyles: Record<TipType, string> = {
|
||||
info: 'bg-gradient-to-br from-purple-500/5 via-cyan-500/5 to-purple-500/5 border-purple-500/30 text-gray-300 shadow-lg shadow-purple-500/10',
|
||||
warning: 'bg-gradient-to-br from-amber-500/5 via-orange-500/5 to-amber-500/5 border-amber-500/30 text-gray-300 shadow-lg shadow-amber-500/10',
|
||||
success: 'bg-gradient-to-br from-green-500/5 via-emerald-500/5 to-green-500/5 border-green-500/30 text-gray-300 shadow-lg shadow-green-500/10',
|
||||
};
|
||||
|
||||
export const TipBox = ({ children, variant = 'compact', type = 'info' }: TipBoxProps) => {
|
||||
const isCompact = variant === 'compact';
|
||||
const icon = icons[type];
|
||||
const styleClass = isCompact ? compactStyles[type] : prominentStyles[type];
|
||||
|
||||
if (isCompact) {
|
||||
return (
|
||||
<div
|
||||
className={`flex gap-3 p-4 rounded-xl border ${styleClass}`}
|
||||
role="note"
|
||||
aria-label={`${type} note`}
|
||||
>
|
||||
<span className="text-lg flex-shrink-0">{icon}</span>
|
||||
<div className="text-sm leading-relaxed">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Prominent style
|
||||
return (
|
||||
<div
|
||||
className={`p-6 rounded-2xl border ${styleClass}`}
|
||||
role="alert"
|
||||
aria-label={`${type} alert`}
|
||||
>
|
||||
<div className="text-base leading-relaxed">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
'use client';
|
||||
|
||||
/**
|
||||
* Subsection Navigation Component
|
||||
*
|
||||
* Reusable navigation bar for documentation and other subsections
|
||||
* Features:
|
||||
* - Dark nav bar with decorative wave line
|
||||
* - Active state indicator (purple underline/highlight)
|
||||
* - "Join Beta" CTA button on the right
|
||||
* - Responsive (hamburger menu on mobile)
|
||||
* - Can be reused across landing app sections
|
||||
*
|
||||
* Usage:
|
||||
* <SubsectionNav
|
||||
* items={[...]}
|
||||
* currentPath="/docs/final"
|
||||
* ctaText="Join Beta"
|
||||
* ctaHref="/signup"
|
||||
* />
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
interface NavItem {
|
||||
label: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface SubsectionNavProps {
|
||||
items: NavItem[];
|
||||
currentPath: string;
|
||||
ctaText?: string;
|
||||
ctaHref?: string;
|
||||
onCtaClick?: () => void;
|
||||
}
|
||||
|
||||
export const SubsectionNav = ({
|
||||
items,
|
||||
currentPath,
|
||||
ctaText = 'Join Beta',
|
||||
ctaHref = '/signup',
|
||||
onCtaClick,
|
||||
}: SubsectionNavProps) => {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
const isActive = (href: string) => currentPath.startsWith(href);
|
||||
|
||||
return (
|
||||
<nav className="sticky top-0 z-40 bg-slate-950/80 backdrop-blur-sm border-b border-white/10">
|
||||
{/* Main Nav Container */}
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="flex items-center h-12">
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex items-center gap-8">
|
||||
{items.map((item) => {
|
||||
const active = isActive(item.href);
|
||||
return (
|
||||
<a
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`
|
||||
py-3 text-sm font-medium transition-colors
|
||||
${active ? 'text-purple-400' : 'text-gray-400 hover:text-white'}
|
||||
`}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<div className="md:hidden flex items-center ml-auto">
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="p-2 text-gray-400 hover:text-white transition-colors"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
{mobileMenuOpen ? (
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Decorative Wave Line */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px overflow-hidden">
|
||||
<svg
|
||||
className="absolute inset-0 w-full h-full"
|
||||
viewBox="0 0 1200 2"
|
||||
preserveAspectRatio="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="wave-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="rgb(147, 51, 234)" stopOpacity="0.3" />
|
||||
<stop offset="50%" stopColor="rgb(8, 145, 178)" stopOpacity="0.5" />
|
||||
<stop offset="100%" stopColor="rgb(147, 51, 234)" stopOpacity="0.3" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M0,1 Q300,0 600,1 T1200,1"
|
||||
stroke="url(#wave-gradient)"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
className="animate-pulse"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Overlay */}
|
||||
{mobileMenuOpen && (
|
||||
<div className="md:hidden border-t border-white/10 bg-slate-900/95 backdrop-blur-sm">
|
||||
<div className="px-6 py-4 space-y-2">
|
||||
{items.map((item) => {
|
||||
const active = isActive(item.href);
|
||||
return (
|
||||
<a
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className={`
|
||||
block px-4 py-3 rounded-lg text-sm font-medium transition-colors
|
||||
${active ? 'bg-purple-500/10 text-purple-400' : 'text-gray-400 hover:text-white hover:bg-white/5'}
|
||||
`}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in New Issue