1"use client";
2
3
13import { useState } from "react";
14import { Check, Copy, FileText, Webhook as WebhookIcon } from "lucide-react";
15import { API_BASE } from "@/lib/constants";
16import { cn } from "@/lib/utils";
17
18const KEY_PLACEHOLDER = "$FACTORY_API_KEY";
19const TOKEN_PLACEHOLDER = "$ACCESS_TOKEN";
20
21function CodeBlock({
22 title,
23 code,
24 testId
25}: {
26 title: string;
27 code: string;
28 testId?: string;
29}) {
30 const [copied, setCopied] = useState(false);
31 const doCopy = async () => {
32 let ok = false;
33 try {
34 await navigator.clipboard.writeText(code);
35 ok = true;
36 } catch {
37 try {
38 const ta = document.createElement("textarea");
39 ta.value = code;
40 ta.setAttribute("readonly", "");
41 ta.style.position = "fixed";
42 ta.style.opacity = "0";
43 document.body.appendChild(ta);
44 ta.select();
45 ok = document.execCommand("copy");
46 document.body.removeChild(ta);
47 } catch {
48 ok = false;
49 }
50 }
51 setCopied(ok);
52 setTimeout(() => setCopied(false), 2000);
53 };
54 return (
55 <div className="rounded-md border border-border">
56 <div className="flex items-center justify-between border-b border-border px-2.5 py-1.5">
57 <p className="text-xs font-medium text-muted-foreground">{title}</p>
58 <button
59 type="button"
60 onClick={doCopy}
61 data-testid={testId}
62 aria-label={`Copy ${title}`}
63 className="inline-flex items-center gap-1 rounded-md border border-border px-2 py-0.5 text-[11px] hover:bg-accent"
64 >
65 {copied ? (
66 <Check className="size-3 text-emerald-600" aria-hidden />
67 ) : (
68 <Copy className="size-3" aria-hidden />
69 )}
70 {copied ? "Copied" : "Copy"}
71 </button>
72 </div>
73 <span role="status" className="sr-only">
74 {copied ? `${title} copied` : ""}
75 </span>
76 <pre className="overflow-x-auto p-2.5 font-mono text-[11px] leading-relaxed">{code}</pre>
77 </div>
78 );
79}
80
81export function OverviewTab({ workspaceSlug }: { workspaceSlug: string }) {
82 const quickstart = `# 1. List your pages (API-key auth)
83curl -s ${API_BASE}/workspaces/${workspaceSlug}/pages \\
84 -H "Authorization: Bearer ${KEY_PLACEHOLDER}"
85
86# 2. Create a page
87curl -s -X POST ${API_BASE}/workspaces/${workspaceSlug}/pages \\
88 -H "Authorization: Bearer ${KEY_PLACEHOLDER}" \\
89 -H 'Content-Type: application/json' \\
90 -d '{"title":"Hello from the API"}'`;
91
92 const apiKey = `# API keys are created in this hub (API keys tab) and shown ONCE.
93# Use them server-to-server with the Authorization header:
94curl -s ${API_BASE}/workspaces/${workspaceSlug}/pages/\\<pageId\\> \\
95 -H "Authorization: Bearer ${KEY_PLACEHOLDER}"
96
97# Scopes: READ < WRITE < ADMIN (ADMIN implies the lower scopes).`;
98
99 const oauth = `# 1. Exchange the authorization code (server-side, secret kept private)
100curl -s -X POST ${API_BASE}/oauth/token \\
101 -H 'Content-Type: application/x-www-form-urlencoded' \\
102 -d 'grant_type=authorization_code&code=\\<code\\>&client_id=\\<clientId\\>&client_secret=\\<clientSecret\\>&redirect_uri=\\<redirectUri\\>'
103
104# 2. Use the access token
105curl -s ${API_BASE}/workspaces/${workspaceSlug}/pages \\
106 -H "Authorization: Bearer $ACCESS_TOKEN"`;
107
108 const webhook = `# Verify a webhook signature (Node):
109const crypto = require("node:crypto");
110function verify(req, secret) {
111 const header = req.headers["x-factory-signature"]; // "t=<ts>,v1=<hex>"
112 const m = header.match(/^t=(\\d+),v1=([0-9a-f]{64})$/);
113 if (!m) return false;
114 const [t, v1] = m.slice(1, 3);
115 if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // ±5 min
116 const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
117 return crypto.timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(expected, "hex"));
118}`;
119
120 return (
121 <div className="space-y-4">
122 <div className="rounded-lg border border-border p-4">
123 <h3 className="text-sm font-semibold">Base URL</h3>
124 <p className="mt-1 text-xs text-muted-foreground">
125 All Factory API endpoints are under this prefix. The OpenAPI UI is at{" "}
126 <a className="underline" href={`${API_BASE}/docs`} target="_blank" rel="noreferrer">
127 {API_BASE}/docs
128 </a>
129 , the markdown reference at{" "}
130 <a className="underline" href="/docs/api" target="_blank" rel="noreferrer">
131 /docs/api
132 </a>
133 .
134 </p>
135 <div className="mt-2 flex items-center gap-2">
136 <code className="flex-1 truncate rounded-md bg-muted px-2.5 py-1.5 font-mono text-xs">
137 {API_BASE}
138 </code>
139 </div>
140 </div>
141
142 <CodeBlock title="Quickstart — create a page" code={quickstart} testId="overview-quickstart" />
143 <CodeBlock title="API-key authentication" code={apiKey} />
144 <CodeBlock title="OAuth2 — token exchange" code={oauth} />
145 <CodeBlock title="Verifying webhook signatures" code={webhook} />
146
147 <div className="flex flex-wrap gap-2 text-xs">
148 <a
149 href="/docs/api"
150 target="_blank"
151 rel="noreferrer"
152 className={cn(
153 "inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1.5 hover:bg-accent"
154 )}
155 >
156 <FileText className="size-3.5" aria-hidden /> API reference (markdown)
157 </a>
158 <a
159 href={`${API_BASE}/docs`}
160 target="_blank"
161 rel="noreferrer"
162 className="inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1.5 hover:bg-accent"
163 >
164 <WebhookIcon className="size-3.5" aria-hidden /> OpenAPI / Swagger
165 </a>
166 </div>
167 </div>
168 );
169}
170