Seed: developer-tools template (15 files)
This commit is contained in:
@@ -0,0 +1,313 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import {
|
||||||
|
BookOpen, Key, Server, AlertTriangle, Gauge, ChevronRight,
|
||||||
|
Terminal, Copy, CheckCircle2, ArrowRight
|
||||||
|
} from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import Header from "@/components/Header";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
|
||||||
|
const sidebarSections = [
|
||||||
|
{ id: "getting-started", label: "Getting Started", icon: BookOpen },
|
||||||
|
{ id: "authentication", label: "Authentication", icon: Key },
|
||||||
|
{ id: "endpoints", label: "Endpoints", icon: Server },
|
||||||
|
{ id: "errors", label: "Error Handling", icon: AlertTriangle },
|
||||||
|
{ id: "rate-limits", label: "Rate Limits", icon: Gauge },
|
||||||
|
];
|
||||||
|
|
||||||
|
const endpoints = [
|
||||||
|
{ method: "GET", path: "/v1/projects", desc: "List all projects" },
|
||||||
|
{ method: "POST", path: "/v1/projects", desc: "Create a new project" },
|
||||||
|
{ method: "GET", path: "/v1/projects/:id", desc: "Get project by ID" },
|
||||||
|
{ method: "POST", path: "/v1/deploys", desc: "Create a deployment" },
|
||||||
|
{ method: "GET", path: "/v1/deploys/:id", desc: "Get deployment status" },
|
||||||
|
{ method: "DELETE", path: "/v1/projects/:id", desc: "Delete a project" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const errorCodes = [
|
||||||
|
{ code: "400", name: "Bad Request", desc: "The request body is malformed or missing required fields." },
|
||||||
|
{ code: "401", name: "Unauthorized", desc: "API key is missing or invalid." },
|
||||||
|
{ code: "403", name: "Forbidden", desc: "API key does not have the required scope." },
|
||||||
|
{ code: "404", name: "Not Found", desc: "The requested resource does not exist." },
|
||||||
|
{ code: "429", name: "Rate Limited", desc: "You have exceeded your rate limit. Retry after the indicated time." },
|
||||||
|
{ code: "500", name: "Internal Error", desc: "Something went wrong on our end. Contact support." },
|
||||||
|
];
|
||||||
|
|
||||||
|
function CodeBlock({ code, language }: { code: string; language?: string }) {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
navigator.clipboard.writeText(code);
|
||||||
|
setCopied(true);
|
||||||
|
toast.success("Copied to clipboard");
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative rounded-lg border border-slate-800 bg-slate-950 overflow-hidden group">
|
||||||
|
{language && (
|
||||||
|
<div className="flex items-center justify-between px-4 py-2 border-b border-slate-800 bg-slate-900/50">
|
||||||
|
<span className="text-xs text-slate-500 font-mono">{language}</span>
|
||||||
|
<button
|
||||||
|
onClick={handleCopy}
|
||||||
|
className="text-slate-500 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{copied ? <CheckCircle2 className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<pre className="p-4 text-sm font-mono leading-relaxed overflow-x-auto text-slate-300">
|
||||||
|
<code>{code}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocsPage() {
|
||||||
|
const [activeSection, setActiveSection] = useState("getting-started");
|
||||||
|
|
||||||
|
const scrollTo = (id: string) => {
|
||||||
|
setActiveSection(id);
|
||||||
|
document.getElementById(id)?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-950 text-white antialiased">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<div className="pt-24 mx-auto max-w-7xl px-6">
|
||||||
|
<div className="flex gap-8">
|
||||||
|
{/* Sidebar */}
|
||||||
|
<aside className="hidden lg:block w-64 flex-shrink-0">
|
||||||
|
<div className="sticky top-28">
|
||||||
|
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-4">Documentation</h3>
|
||||||
|
<ScrollArea className="h-[calc(100vh-10rem)]">
|
||||||
|
<nav className="space-y-1">
|
||||||
|
{sidebarSections.map((s) => (
|
||||||
|
<button
|
||||||
|
key={s.id}
|
||||||
|
onClick={() => scrollTo(s.id)}
|
||||||
|
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-all \${
|
||||||
|
activeSection === s.id
|
||||||
|
? "bg-emerald-500/10 text-emerald-400 font-medium"
|
||||||
|
: "text-slate-400 hover:text-white hover:bg-slate-800/50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<s.icon className="h-4 w-4" />
|
||||||
|
{s.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="flex-1 min-w-0 pb-24">
|
||||||
|
{/* Mobile nav */}
|
||||||
|
<div className="lg:hidden flex gap-2 overflow-x-auto pb-4 mb-8 border-b border-slate-800">
|
||||||
|
{sidebarSections.map((s) => (
|
||||||
|
<button
|
||||||
|
key={s.id}
|
||||||
|
onClick={() => scrollTo(s.id)}
|
||||||
|
className={`flex-shrink-0 px-3 py-1.5 rounded-full text-sm \${
|
||||||
|
activeSection === s.id
|
||||||
|
? "bg-emerald-500/10 text-emerald-400"
|
||||||
|
: "text-slate-400 hover:text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{s.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Getting Started */}
|
||||||
|
<section id="getting-started" className="mb-16">
|
||||||
|
<Badge className="mb-4 bg-emerald-500/10 text-emerald-400 border-emerald-500/20">Getting Started</Badge>
|
||||||
|
<h1 className="text-3xl font-bold mb-4 animate-fade-up">Welcome to DevStack Docs</h1>
|
||||||
|
<p className="text-slate-400 text-lg mb-8 leading-relaxed">
|
||||||
|
Get up and running with the DevStack API in minutes. This guide covers installation, authentication, and your first API call.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mb-4 text-white">1. Install the SDK</h3>
|
||||||
|
<CodeBlock language="bash" code="npm install @devstack/sdk" />
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mb-4 mt-8 text-white">2. Initialize the client</h3>
|
||||||
|
<CodeBlock
|
||||||
|
language="javascript"
|
||||||
|
code={`import { DevStack } from '@devstack/sdk';
|
||||||
|
|
||||||
|
const client = new DevStack({
|
||||||
|
apiKey: process.env.DEVSTACK_API_KEY,
|
||||||
|
});`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mb-4 mt-8 text-white">3. Make your first API call</h3>
|
||||||
|
<CodeBlock
|
||||||
|
language="javascript"
|
||||||
|
code={`const projects = await client.projects.list();
|
||||||
|
console.log(projects);
|
||||||
|
|
||||||
|
// Create a new project
|
||||||
|
const project = await client.projects.create({
|
||||||
|
name: 'my-awesome-app',
|
||||||
|
framework: 'nextjs',
|
||||||
|
});`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Authentication */}
|
||||||
|
<section id="authentication" className="mb-16 pt-8 border-t border-slate-800">
|
||||||
|
<Badge className="mb-4 bg-sky-500/10 text-sky-400 border-sky-500/20">Authentication</Badge>
|
||||||
|
<h2 className="text-2xl font-bold mb-4 animate-fade-up">Authentication</h2>
|
||||||
|
<p className="text-slate-400 mb-6 leading-relaxed">
|
||||||
|
All API requests require a valid API key. Include your key in the Authorization header.
|
||||||
|
</p>
|
||||||
|
<CodeBlock
|
||||||
|
language="http"
|
||||||
|
code={`Authorization: Bearer sk_live_your_api_key_here
|
||||||
|
|
||||||
|
# Or pass via query parameter (not recommended)
|
||||||
|
GET /v1/projects?api_key=sk_live_...`}
|
||||||
|
/>
|
||||||
|
<Card className="mt-6 bg-amber-500/5 border-amber-500/20">
|
||||||
|
<CardContent className="p-4 flex items-start gap-3">
|
||||||
|
<AlertTriangle className="h-5 w-5 text-amber-400 flex-shrink-0 mt-0.5" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-amber-300">Keep your API keys secure</p>
|
||||||
|
<p className="text-sm text-slate-400 mt-1">Never expose your secret API key in client-side code. Use environment variables and server-side requests only.</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Endpoints */}
|
||||||
|
<section id="endpoints" className="mb-16 pt-8 border-t border-slate-800">
|
||||||
|
<Badge className="mb-4 bg-violet-500/10 text-violet-400 border-violet-500/20">API Reference</Badge>
|
||||||
|
<h2 className="text-2xl font-bold mb-4 animate-fade-up">Endpoints</h2>
|
||||||
|
<p className="text-slate-400 mb-6 leading-relaxed">
|
||||||
|
Base URL: <code className="text-emerald-400 font-mono text-sm bg-emerald-500/10 px-2 py-1 rounded">https://api.devstack.io</code>
|
||||||
|
</p>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{endpoints.map((ep) => (
|
||||||
|
<div key={ep.path + ep.method} className="flex items-center gap-4 p-4 rounded-lg border border-slate-800 bg-slate-900/50 hover:border-emerald-500/20 transition-all">
|
||||||
|
<Badge className={`font-mono text-xs px-2 py-1 \${
|
||||||
|
ep.method === "GET" ? "bg-sky-500/10 text-sky-400 border-sky-500/20" :
|
||||||
|
ep.method === "POST" ? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20" :
|
||||||
|
"bg-red-500/10 text-red-400 border-red-500/20"
|
||||||
|
}`}>
|
||||||
|
{ep.method}
|
||||||
|
</Badge>
|
||||||
|
<code className="text-sm font-mono text-slate-300">{ep.path}</code>
|
||||||
|
<span className="text-sm text-slate-500 ml-auto hidden sm:block">{ep.desc}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mb-4 mt-8 text-white">Example Request</h3>
|
||||||
|
<CodeBlock
|
||||||
|
language="javascript"
|
||||||
|
code={`const response = await fetch('https://api.devstack.io/v1/projects', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer sk_live_...',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: 'my-app',
|
||||||
|
framework: 'nextjs',
|
||||||
|
region: 'us-east-1',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const project = await response.json();`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mb-4 mt-8 text-white">Example Response</h3>
|
||||||
|
<CodeBlock
|
||||||
|
language="json"
|
||||||
|
code={`{
|
||||||
|
"id": "proj_abc123",
|
||||||
|
"name": "my-app",
|
||||||
|
"framework": "nextjs",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"status": "active",
|
||||||
|
"url": "https://my-app.devstack.io",
|
||||||
|
"created_at": "2024-01-15T10:30:00Z"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Errors */}
|
||||||
|
<section id="errors" className="mb-16 pt-8 border-t border-slate-800">
|
||||||
|
<Badge className="mb-4 bg-red-500/10 text-red-400 border-red-500/20">Errors</Badge>
|
||||||
|
<h2 className="text-2xl font-bold mb-4 animate-fade-up">Error Handling</h2>
|
||||||
|
<p className="text-slate-400 mb-6 leading-relaxed">
|
||||||
|
The API uses standard HTTP status codes to indicate success or failure.
|
||||||
|
</p>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{errorCodes.map((e) => (
|
||||||
|
<div key={e.code} className="flex items-start gap-4 p-4 rounded-lg border border-slate-800 bg-slate-900/50">
|
||||||
|
<Badge variant="outline" className={`font-mono text-xs \${
|
||||||
|
e.code.startsWith("4") ? "text-amber-400 border-amber-500/30" : "text-red-400 border-red-500/30"
|
||||||
|
}`}>
|
||||||
|
{e.code}
|
||||||
|
</Badge>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-white">{e.name}</p>
|
||||||
|
<p className="text-sm text-slate-400 mt-1">{e.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Rate Limits */}
|
||||||
|
<section id="rate-limits" className="mb-16 pt-8 border-t border-slate-800">
|
||||||
|
<Badge className="mb-4 bg-orange-500/10 text-orange-400 border-orange-500/20">Rate Limits</Badge>
|
||||||
|
<h2 className="text-2xl font-bold mb-4 animate-fade-up">Rate Limits</h2>
|
||||||
|
<p className="text-slate-400 mb-6 leading-relaxed">
|
||||||
|
Rate limits vary by plan. Exceeding limits returns a 429 response with a Retry-After header.
|
||||||
|
</p>
|
||||||
|
<div className="grid sm:grid-cols-3 gap-4">
|
||||||
|
<Card className="bg-slate-900/60 border-slate-800">
|
||||||
|
<CardContent className="p-6 text-center">
|
||||||
|
<p className="text-2xl font-bold text-white">100</p>
|
||||||
|
<p className="text-sm text-slate-400 mt-1">req/min (Free)</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="bg-slate-900/60 border-emerald-500/30">
|
||||||
|
<CardContent className="p-6 text-center">
|
||||||
|
<p className="text-2xl font-bold text-emerald-400">1,000</p>
|
||||||
|
<p className="text-sm text-slate-400 mt-1">req/min (Pro)</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="bg-slate-900/60 border-slate-800">
|
||||||
|
<CardContent className="p-6 text-center">
|
||||||
|
<p className="text-2xl font-bold text-white">Custom</p>
|
||||||
|
<p className="text-sm text-slate-400 mt-1">req/min (Enterprise)</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<CodeBlock
|
||||||
|
language="http"
|
||||||
|
code={`HTTP/1.1 429 Too Many Requests
|
||||||
|
Retry-After: 30
|
||||||
|
X-RateLimit-Limit: 100
|
||||||
|
X-RateLimit-Remaining: 0
|
||||||
|
X-RateLimit-Reset: 1705312800`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user