import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { listAPIKeys, createAPIKey, deleteAPIKey } from '../api'; import { APIKey, APIKeyCreateResponse } from '../types'; import './APIKeysPage.css'; function APIKeysPage() { const { user, loading: authLoading } = useAuth(); const navigate = useNavigate(); const [keys, setKeys] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showCreateForm, setShowCreateForm] = useState(false); const [createName, setCreateName] = useState(''); const [createDescription, setCreateDescription] = useState(''); const [isCreating, setIsCreating] = useState(false); const [createError, setCreateError] = useState(null); const [newlyCreatedKey, setNewlyCreatedKey] = useState(null); const [copied, setCopied] = useState(false); const [deleteConfirmId, setDeleteConfirmId] = useState(null); const [isDeleting, setIsDeleting] = useState(false); useEffect(() => { if (!authLoading && !user) { navigate('/login', { state: { from: '/settings/api-keys' } }); } }, [user, authLoading, navigate]); useEffect(() => { if (user) { loadKeys(); } }, [user]); async function loadKeys() { setLoading(true); setError(null); try { const data = await listAPIKeys(); setKeys(data); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load API keys'); } finally { setLoading(false); } } async function handleCreate(e: React.FormEvent) { e.preventDefault(); if (!createName.trim()) { setCreateError('Name is required'); return; } setIsCreating(true); setCreateError(null); try { const response = await createAPIKey({ name: createName.trim(), description: createDescription.trim() || undefined, }); setNewlyCreatedKey(response); setShowCreateForm(false); setCreateName(''); setCreateDescription(''); await loadKeys(); } catch (err) { setCreateError(err instanceof Error ? err.message : 'Failed to create API key'); } finally { setIsCreating(false); } } async function handleDelete(id: string) { setIsDeleting(true); try { await deleteAPIKey(id); setDeleteConfirmId(null); await loadKeys(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to revoke API key'); } finally { setIsDeleting(false); } } async function handleCopyKey() { if (newlyCreatedKey) { try { await navigator.clipboard.writeText(newlyCreatedKey.key); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { setError('Failed to copy to clipboard'); } } } function handleDismissNewKey() { setNewlyCreatedKey(null); setCopied(false); } function formatDate(dateString: string | null): string { if (!dateString) return 'Never'; return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); } if (authLoading) { return (
Loading...
); } if (!user) { return null; } return (

API Keys

Manage API keys for programmatic access to Orchard

{error && (
{error}
)} {newlyCreatedKey && (
New API Key Created
Copy this key now! It won't be shown again.
{newlyCreatedKey.key}
)} {showCreateForm && (

Create New API Key

{createError && (
{createError}
)}
setCreateName(e.target.value)} placeholder="e.g., CI/CD Pipeline, Local Development" autoFocus disabled={isCreating} />
setCreateDescription(e.target.value)} placeholder="What will this key be used for?" disabled={isCreating} />
)}
{loading ? (
Loading API keys...
) : keys.length === 0 ? (

No API Keys

Create an API key to access Orchard programmatically

) : (
Name Created Last Used Actions
{keys.map((key) => (
{key.name}
{key.description && (
{key.description}
)}
{formatDate(key.created_at)}
{formatDate(key.last_used)}
{deleteConfirmId === key.id ? (
Revoke?
) : ( )}
))}
)}
); } export default APIKeysPage;