Files
app-service/frontend/src/pages/APIKeys.tsx
matteoscrugli 6faeb3c975 Remove icons from page title bars and fix Features page issues
- Remove icons from all page title sections in tab bars
- Fix double padding on mobile for Features page
- Fix drag state not resetting when Apply/Cancel pressed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 23:07:04 +01:00

189 lines
6.0 KiB
TypeScript

import { useEffect, useState } from 'react';
import { useTranslation } from '../contexts/LanguageContext';
import { useSidebar } from '../contexts/SidebarContext';
import { apiKeysAPI } from '../api/client';
import type { ApiKeyItem } from '../api/client';
import '../styles/APIKeys.css';
export default function APIKeys() {
const { t } = useTranslation();
const { toggleMobileMenu } = useSidebar();
const [items, setItems] = useState<ApiKeyItem[]>([]);
const [loading, setLoading] = useState(true);
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [name, setName] = useState('');
const [createdKey, setCreatedKey] = useState<string | null>(null);
const load = async () => {
setLoading(true);
setError('');
try {
const data = await apiKeysAPI.list();
setItems(data.items || []);
} catch (err: any) {
setError(err?.response?.data?.detail || t.common.error);
} finally {
setLoading(false);
}
};
useEffect(() => {
load();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const create = async () => {
setBusy(true);
setError('');
setCreatedKey(null);
try {
const created = await apiKeysAPI.create({ name: name.trim() });
setCreatedKey(created.key);
setName('');
await load();
} catch (err: any) {
setError(err?.response?.data?.detail || t.common.error);
} finally {
setBusy(false);
}
};
const revoke = async (id: string) => {
setBusy(true);
setError('');
try {
await apiKeysAPI.revoke(id);
await load();
} catch (err: any) {
setError(err?.response?.data?.detail || t.common.error);
} finally {
setBusy(false);
}
};
const deleteKey = async (id: string) => {
setBusy(true);
setError('');
try {
await apiKeysAPI.delete(id);
await load();
} catch (err: any) {
setError(err?.response?.data?.detail || t.common.error);
} finally {
setBusy(false);
}
};
const copy = async () => {
if (!createdKey) return;
try {
await navigator.clipboard.writeText(createdKey);
} catch {
// ignore
}
};
return (
<main className="main-content api-keys-root">
<div className="page-tabs-container">
<div className="page-tabs-slider">
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
<span className="material-symbols-outlined">menu</span>
</button>
<div className="page-title-section">
<span className="page-title-text">{t.apiKeysPage.title}</span>
</div>
</div>
</div>
<div className="page-content">
{error && <div className="error-message">{error}</div>}
<div className="api-keys-section">
<h3 className="section-title">{t.apiKeysPage.createTitle}</h3>
<p className="api-keys-desc">{t.apiKeysPage.createDesc}</p>
<div className="api-keys-create-row">
<input
className="api-keys-input"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={t.apiKeysPage.namePlaceholder}
disabled={busy}
/>
<button className="btn-primary" onClick={create} disabled={busy || name.trim().length < 1}>
{t.apiKeysPage.createButton}
</button>
</div>
{createdKey && (
<div className="api-keys-created">
<div className="api-keys-created-header">
<span className="badge badge-accent">{t.apiKeysPage.showOnce}</span>
<button className="btn-link" onClick={copy}>{t.apiKeysPage.copy}</button>
</div>
<code className="api-keys-created-key">{createdKey}</code>
</div>
)}
</div>
<div className="api-keys-section">
<h3 className="section-title">{t.apiKeysPage.listTitle}</h3>
{loading ? (
<div className="loading">{t.common.loading}</div>
) : items.length === 0 ? (
<div className="api-keys-empty">{t.apiKeysPage.empty}</div>
) : (
<div className="api-keys-table-card">
<table className="api-keys-table">
<thead>
<tr>
<th>{t.apiKeysPage.name}</th>
<th>{t.apiKeysPage.prefix}</th>
<th>{t.apiKeysPage.status}</th>
<th>{t.apiKeysPage.lastUsed}</th>
<th>{t.apiKeysPage.usage}</th>
<th>{t.apiKeysPage.actions}</th>
</tr>
</thead>
<tbody>
{items.map((k) => (
<tr key={k.id}>
<td>{k.name}</td>
<td className="mono">{k.key_prefix}</td>
<td>
<span className={`badge ${k.is_active ? 'badge-success' : 'badge-muted'}`}>
{k.is_active ? t.settings.enabled : t.settings.disabled}
</span>
</td>
<td className="mono">{k.last_used_at ? new Date(k.last_used_at).toLocaleString() : '—'}</td>
<td className="mono">{k.usage_count}</td>
<td className="api-keys-actions">
{k.is_active ? (
<button className="btn-link" onClick={() => revoke(k.id)} disabled={busy}>
{t.apiKeysPage.revoke}
</button>
) : (
<span className="api-keys-muted">{t.apiKeysPage.revoked}</span>
)}
<button className="btn-link danger" onClick={() => deleteKey(k.id)} disabled={busy}>
{t.common.delete}
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
</main>
);
}