Seth
update
3d5a179
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
Key,
Plus,
Trash2,
Copy,
Check,
Eye,
EyeOff,
Code,
FileText,
AlertCircle,
Clock,
Sparkles,
ExternalLink,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { createAPIKey, listAPIKeys, deleteAPIKey } from "@/services/api";
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "https://seth0330-ezofisocr.hf.space";
export default function APIKeys() {
const [apiKeys, setApiKeys] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [showCreateModal, setShowCreateModal] = useState(false);
const [newKeyName, setNewKeyName] = useState("");
const [isCreating, setIsCreating] = useState(false);
const [newlyCreatedKey, setNewlyCreatedKey] = useState(null);
const [copiedKeyId, setCopiedKeyId] = useState(null);
const [visibleKeys, setVisibleKeys] = useState(new Set());
// Fetch API keys on mount
useEffect(() => {
fetchAPIKeys();
}, []);
const fetchAPIKeys = async () => {
setIsLoading(true);
setError(null);
try {
const response = await listAPIKeys();
setApiKeys(response.api_keys || []);
} catch (err) {
setError(err.message || "Failed to load API keys");
} finally {
setIsLoading(false);
}
};
const handleCreateKey = async () => {
if (!newKeyName.trim()) {
setError("Please enter a name for your API key");
return;
}
setIsCreating(true);
setError(null);
try {
const response = await createAPIKey(newKeyName.trim());
setNewlyCreatedKey(response.api_key);
setNewKeyName("");
setShowCreateModal(false);
await fetchAPIKeys();
} catch (err) {
setError(err.message || "Failed to create API key");
} finally {
setIsCreating(false);
}
};
const handleDeleteKey = async (keyId) => {
if (!confirm("Are you sure you want to deactivate this API key? This action cannot be undone.")) {
return;
}
try {
await deleteAPIKey(keyId);
await fetchAPIKeys();
} catch (err) {
setError(err.message || "Failed to delete API key");
}
};
const copyToClipboard = (text, keyId = null) => {
navigator.clipboard.writeText(text);
if (keyId) {
setCopiedKeyId(keyId);
setTimeout(() => setCopiedKeyId(null), 2000);
}
};
const toggleKeyVisibility = (keyId) => {
const newVisible = new Set(visibleKeys);
if (newVisible.has(keyId)) {
newVisible.delete(keyId);
} else {
newVisible.add(keyId);
}
setVisibleKeys(newVisible);
};
const formatDate = (dateString) => {
if (!dateString) return "Never";
const date = new Date(dateString);
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
return (
<div className="min-h-screen bg-[#FAFAFA]">
{/* Header */}
<header className="bg-white border-b border-slate-200/80 sticky top-0 z-40 h-16">
<div className="px-8 h-full flex items-center justify-between">
<div>
<h1 className="text-xl font-bold text-slate-900 tracking-tight leading-tight">
API Keys
</h1>
<p className="text-sm text-slate-500 leading-tight">
Manage API keys for external application access
</p>
</div>
<Button
onClick={() => setShowCreateModal(true)}
className="h-10 px-6 rounded-xl font-semibold bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700 shadow-lg shadow-indigo-500/25 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-300"
>
<Plus className="h-4 w-4 mr-2" />
Create API Key
</Button>
</div>
</header>
{/* Main Content */}
<div className="p-8">
{/* Error Message */}
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-4xl mx-auto mb-6"
>
<div className="bg-red-50 border border-red-200 rounded-2xl p-4 flex items-start gap-3">
<AlertCircle className="h-5 w-5 text-red-600 flex-shrink-0 mt-0.5" />
<div className="flex-1">
<h3 className="font-semibold text-red-900 mb-1">Error</h3>
<p className="text-sm text-red-700">{error}</p>
</div>
<button
onClick={() => setError(null)}
className="text-red-400 hover:text-red-600 transition-colors"
>
×
</button>
</div>
</motion.div>
)}
{/* Success Message for New Key */}
{newlyCreatedKey && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-4xl mx-auto mb-6"
>
<div className="bg-emerald-50 border border-emerald-200 rounded-2xl p-6">
<div className="flex items-start gap-3 mb-4">
<div className="h-10 w-10 rounded-xl bg-emerald-100 flex items-center justify-center flex-shrink-0">
<Check className="h-5 w-5 text-emerald-600" />
</div>
<div className="flex-1">
<h3 className="font-semibold text-emerald-900 mb-1">
API Key Created Successfully!
</h3>
<p className="text-sm text-emerald-700 mb-4">
⚠️ Store this key securely - it will not be shown again.
</p>
<div className="bg-white rounded-xl p-4 border border-emerald-200">
<div className="flex items-center gap-2 mb-2">
<Key className="h-4 w-4 text-slate-500" />
<span className="text-xs font-medium text-slate-500 uppercase tracking-wide">
Your API Key
</span>
</div>
<div className="flex items-center gap-2">
<code className="flex-1 font-mono text-sm text-slate-900 break-all">
{newlyCreatedKey}
</code>
<Button
size="sm"
variant="outline"
onClick={() => copyToClipboard(newlyCreatedKey, "new")}
className="flex-shrink-0"
>
{copiedKeyId === "new" ? (
<Check className="h-4 w-4 text-emerald-600" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</div>
</div>
</div>
</div>
<Button
onClick={() => setNewlyCreatedKey(null)}
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white"
>
I've Saved My Key
</Button>
</div>
</motion.div>
)}
{/* API Keys List */}
<div className="max-w-4xl mx-auto">
{isLoading ? (
<div className="bg-white rounded-2xl border border-slate-200 p-12 text-center">
<div className="h-12 w-12 mx-auto rounded-2xl bg-indigo-100 flex items-center justify-center mb-4 animate-pulse">
<Key className="h-6 w-6 text-indigo-600" />
</div>
<p className="text-slate-600">Loading API keys...</p>
</div>
) : apiKeys.length === 0 ? (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-2xl border border-slate-200 p-12 text-center"
>
<div className="h-16 w-16 mx-auto rounded-2xl bg-slate-100 flex items-center justify-center mb-4">
<Key className="h-8 w-8 text-slate-400" />
</div>
<h3 className="text-lg font-semibold text-slate-900 mb-2">
No API Keys Yet
</h3>
<p className="text-slate-500 mb-6">
Create your first API key to start using the API from external applications
</p>
<Button
onClick={() => setShowCreateModal(true)}
className="h-11 px-6 rounded-xl font-semibold bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700"
>
<Plus className="h-4 w-4 mr-2" />
Create Your First API Key
</Button>
</motion.div>
) : (
<div className="space-y-4">
{apiKeys.map((key) => (
<motion.div
key={key.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-2xl border border-slate-200 p-6 hover:shadow-lg transition-shadow"
>
<div className="flex items-start justify-between mb-4">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<Key className="h-5 w-5 text-indigo-600" />
<h3 className="font-semibold text-slate-900">{key.name}</h3>
{key.is_active ? (
<Badge className="bg-emerald-50 text-emerald-700 border-emerald-200">
Active
</Badge>
) : (
<Badge className="bg-slate-100 text-slate-600 border-slate-200">
Inactive
</Badge>
)}
</div>
<div className="ml-8 space-y-2">
<div className="flex items-center gap-2">
<span className="font-mono text-sm text-slate-500 break-all">
{key.key_prefix}
</span>
</div>
<div className="flex items-center gap-4 text-xs text-slate-400">
<div className="flex items-center gap-1">
<Clock className="h-3 w-3" />
<span>Created: {formatDate(key.created_at)}</span>
</div>
{key.last_used_at && (
<div className="flex items-center gap-1">
<Sparkles className="h-3 w-3" />
<span>Last used: {formatDate(key.last_used_at)}</span>
</div>
)}
</div>
</div>
</div>
<Button
size="sm"
variant="ghost"
onClick={() => handleDeleteKey(key.id)}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
disabled={!key.is_active}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</motion.div>
))}
</div>
)}
</div>
{/* API Usage Guide */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="max-w-4xl mx-auto mt-12"
>
<div className="bg-white rounded-2xl border border-slate-200 p-8">
<div className="flex items-center gap-3 mb-6">
<div className="h-12 w-12 rounded-xl bg-indigo-50 flex items-center justify-center">
<Code className="h-6 w-6 text-indigo-600" />
</div>
<div>
<h2 className="text-xl font-bold text-slate-900">How to Use API Keys</h2>
<p className="text-sm text-slate-500">
Integrate document parsing into your external applications
</p>
</div>
</div>
<div className="space-y-6">
{/* Python Example */}
<div className="bg-slate-50 rounded-xl p-6 border border-slate-200">
<div className="flex items-center gap-2 mb-4">
<FileText className="h-5 w-5 text-slate-600" />
<h3 className="font-semibold text-slate-900">Python Example</h3>
</div>
<pre className="bg-slate-900 text-slate-100 rounded-lg p-4 overflow-x-auto text-sm">
<code>{`import requests
API_URL = "${API_BASE_URL}"
API_KEY = "sk_live_YOUR_API_KEY_HERE"
def extract_document(file_path, key_fields=None):
with open(file_path, 'rb') as f:
files = {'file': f}
data = {}
if key_fields:
data['key_fields'] = key_fields
response = requests.post(
f"{API_URL}/api/extract",
headers={"X-API-Key": API_KEY},
files=files,
data=data
)
return response.json()
# Usage
result = extract_document("invoice.pdf",
key_fields="Invoice Number,Invoice Date")
print(result)`}</code>
</pre>
<Button
size="sm"
variant="outline"
onClick={() => copyToClipboard(`import requests
API_URL = "${API_BASE_URL}"
API_KEY = "sk_live_YOUR_API_KEY_HERE"
def extract_document(file_path, key_fields=None):
with open(file_path, 'rb') as f:
files = {'file': f}
data = {}
if key_fields:
data['key_fields'] = key_fields
response = requests.post(
f"{API_URL}/api/extract",
headers={"X-API-Key": API_KEY},
files=files,
data=data
)
return response.json()
# Usage
result = extract_document("invoice.pdf",
key_fields="Invoice Number,Invoice Date")
print(result)`)}
className="mt-3"
>
<Copy className="h-3 w-3 mr-2" />
Copy Code
</Button>
</div>
{/* cURL Example */}
<div className="bg-slate-50 rounded-xl p-6 border border-slate-200">
<div className="flex items-center gap-2 mb-4">
<FileText className="h-5 w-5 text-slate-600" />
<h3 className="font-semibold text-slate-900">cURL Example</h3>
</div>
<pre className="bg-slate-900 text-slate-100 rounded-lg p-4 overflow-x-auto text-sm">
<code>{`curl -X POST ${API_BASE_URL}/api/extract \\
-H "X-API-Key: sk_live_YOUR_API_KEY_HERE" \\
-F "file=@document.pdf" \\
-F "key_fields=Invoice Number,Invoice Date,Total Amount"`}</code>
</pre>
<Button
size="sm"
variant="outline"
onClick={() => copyToClipboard(`curl -X POST ${API_BASE_URL}/api/extract \\
-H "X-API-Key: sk_live_YOUR_API_KEY_HERE" \\
-F "file=@document.pdf" \\
-F "key_fields=Invoice Number,Invoice Date,Total Amount"`)}
className="mt-3"
>
<Copy className="h-3 w-3 mr-2" />
Copy Code
</Button>
</div>
{/* JavaScript Example */}
<div className="bg-slate-50 rounded-xl p-6 border border-slate-200">
<div className="flex items-center gap-2 mb-4">
<FileText className="h-5 w-5 text-slate-600" />
<h3 className="font-semibold text-slate-900">JavaScript/Node.js Example</h3>
</div>
<pre className="bg-slate-900 text-slate-100 rounded-lg p-4 overflow-x-auto text-sm">
<code>{`const FormData = require('form-data');
const fs = require('fs');
const axios = require('axios');
const API_URL = '${API_BASE_URL}';
const API_KEY = 'sk_live_YOUR_API_KEY_HERE';
async function extractDocument(filePath, keyFields = null) {
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
if (keyFields) {
form.append('key_fields', keyFields);
}
const response = await axios.post(
\`\${API_URL}/api/extract\`,
form,
{
headers: {
'X-API-Key': API_KEY,
...form.getHeaders()
}
}
);
return response.data;
}
// Usage
extractDocument('invoice.pdf', 'Invoice Number,Invoice Date')
.then(result => console.log(result));`}</code>
</pre>
<Button
size="sm"
variant="outline"
onClick={() => copyToClipboard(`const FormData = require('form-data');
const fs = require('fs');
const axios = require('axios');
const API_URL = '${API_BASE_URL}';
const API_KEY = 'sk_live_YOUR_API_KEY_HERE';
async function extractDocument(filePath, keyFields = null) {
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
if (keyFields) {
form.append('key_fields', keyFields);
}
const response = await axios.post(
\`\${API_URL}/api/extract\`,
form,
{
headers: {
'X-API-Key': API_KEY,
...form.getHeaders()
}
}
);
return response.data;
}
// Usage
extractDocument('invoice.pdf', 'Invoice Number,Invoice Date')
.then(result => console.log(result));`)}
className="mt-3"
>
<Copy className="h-3 w-3 mr-2" />
Copy Code
</Button>
</div>
</div>
<div className="mt-6 p-4 bg-indigo-50 rounded-xl border border-indigo-200">
<div className="flex items-start gap-3">
<AlertCircle className="h-5 w-5 text-indigo-600 flex-shrink-0 mt-0.5" />
<div className="flex-1">
<h4 className="font-semibold text-indigo-900 mb-1">API Endpoint</h4>
<p className="text-sm text-indigo-700 mb-2">
<code className="bg-white px-2 py-1 rounded text-indigo-900">
POST {API_BASE_URL}/api/extract
</code>
</p>
<p className="text-xs text-indigo-600">
• Max file size: 4 MB • Supported formats: PDF, PNG, JPEG, TIFF
</p>
</div>
</div>
</div>
</div>
</motion.div>
</div>
{/* Create API Key Modal */}
<AnimatePresence>
{showCreateModal && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4"
onClick={() => !isCreating && setShowCreateModal(false)}
>
<motion.div
initial={{ scale: 0.95, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.95, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
className="bg-white rounded-2xl p-6 max-w-md w-full shadow-2xl"
>
<h2 className="text-xl font-bold text-slate-900 mb-2">Create New API Key</h2>
<p className="text-sm text-slate-500 mb-6">
Give your API key a descriptive name to identify it later
</p>
<Input
placeholder="e.g., Production API, Test Environment"
value={newKeyName}
onChange={(e) => setNewKeyName(e.target.value)}
className="mb-6"
onKeyPress={(e) => e.key === "Enter" && handleCreateKey()}
/>
<div className="flex gap-3">
<Button
variant="outline"
onClick={() => {
setShowCreateModal(false);
setNewKeyName("");
}}
disabled={isCreating}
className="flex-1"
>
Cancel
</Button>
<Button
onClick={handleCreateKey}
disabled={isCreating || !newKeyName.trim()}
className="flex-1 bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700"
>
{isCreating ? "Creating..." : "Create Key"}
</Button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}