diff --git a/src/app/admin/settings/tabs/ApiTab/ApiTab.tsx b/src/app/admin/settings/tabs/ApiTab/ApiTab.tsx index 64ac43b..f57bfb0 100644 --- a/src/app/admin/settings/tabs/ApiTab/ApiTab.tsx +++ b/src/app/admin/settings/tabs/ApiTab/ApiTab.tsx @@ -8,6 +8,8 @@ import { useState, useEffect, useCallback } from 'react'; import { fetchWithAuth } from '@/lib/utils/api'; import { ConfirmDialog } from '@/app/admin/components/ConfirmDialog'; +import { useApiTokens } from '@/lib/hooks/useApiTokens'; +import { getInstanceUrl } from '@/lib/utils/client-url'; import Link from 'next/link'; import type { AdminApiToken } from '@/lib/types/api-tokens'; @@ -18,34 +20,12 @@ interface UserOption { } export function ApiTab() { - const [tokens, setTokens] = useState([]); + const api = useApiTokens({ basePath: '/api/admin/api-tokens' }); + + // Admin-specific state const [users, setUsers] = useState([]); - const [loading, setLoading] = useState(true); - const [creating, setCreating] = useState(false); - const [newTokenName, setNewTokenName] = useState(''); - const [newTokenExpiry, setNewTokenExpiry] = useState('never'); const [newTokenUserId, setNewTokenUserId] = useState(''); const [newTokenRole, setNewTokenRole] = useState(''); - const [showCreateForm, setShowCreateForm] = useState(false); - const [createdToken, setCreatedToken] = useState(null); - const [copied, setCopied] = useState(false); - const [error, setError] = useState(null); - const [deletingId, setDeletingId] = useState(null); - const [confirmRevokeId, setConfirmRevokeId] = useState(null); - - const fetchTokens = useCallback(async () => { - try { - const response = await fetchWithAuth('/api/admin/api-tokens'); - if (response.ok) { - const data = await response.json(); - setTokens(data.tokens); - } - } catch { - setError('Failed to load API tokens'); - } finally { - setLoading(false); - } - }, []); const fetchUsers = useCallback(async () => { try { @@ -60,110 +40,21 @@ export function ApiTab() { }, []); useEffect(() => { - fetchTokens(); fetchUsers(); - }, [fetchTokens, fetchUsers]); + }, [fetchUsers]); const handleCreate = async () => { - if (!newTokenName.trim()) { - setError('Token name is required'); - return; - } - - setCreating(true); - setError(null); - - try { - let expiresAt: string | null = null; - if (newTokenExpiry !== 'never') { - const date = new Date(); - switch (newTokenExpiry) { - case '30d': date.setDate(date.getDate() + 30); break; - case '90d': date.setDate(date.getDate() + 90); break; - case '1y': date.setFullYear(date.getFullYear() + 1); break; - } - expiresAt = date.toISOString(); - } - - const body: Record = { name: newTokenName.trim(), expiresAt }; - if (newTokenUserId) body.userId = newTokenUserId; - if (newTokenRole) body.role = newTokenRole; - - const response = await fetchWithAuth('/api/admin/api-tokens', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }); - - if (response.ok) { - const data = await response.json(); - setCreatedToken(data.fullToken); - setNewTokenName(''); - setNewTokenExpiry('never'); - setNewTokenUserId(''); - setNewTokenRole(''); - setShowCreateForm(false); - await fetchTokens(); - } else { - const data = await response.json(); - setError(data.error || 'Failed to create token'); - } - } catch { - setError('Failed to create token'); - } finally { - setCreating(false); + const extraBody: Record = {}; + if (newTokenUserId) extraBody.userId = newTokenUserId; + if (newTokenRole) extraBody.role = newTokenRole; + await api.handleCreate(extraBody); + // Reset admin-specific fields on success + if (!api.error) { + setNewTokenUserId(''); + setNewTokenRole(''); } }; - const handleDeleteConfirmed = async () => { - const id = confirmRevokeId; - if (!id) return; - - setConfirmRevokeId(null); - setDeletingId(id); - setError(null); - - try { - const response = await fetchWithAuth(`/api/admin/api-tokens/${id}`, { - method: 'DELETE', - }); - - if (response.ok) { - setTokens(tokens.filter((t) => t.id !== id)); - } else { - setError('Failed to revoke token'); - } - } catch { - setError('Failed to revoke token'); - } finally { - setDeletingId(null); - } - }; - - const handleCopy = async () => { - if (createdToken) { - try { - await navigator.clipboard.writeText(createdToken); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } catch { - setError('Failed to copy to clipboard. Please select and copy the token manually.'); - } - } - }; - - const formatDate = (dateStr: string | null) => { - if (!dateStr) return 'Never'; - return new Date(dateStr).toLocaleDateString(undefined, { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }); - }; - - // When a user is selected, default the role to their actual role const handleUserChange = (userId: string) => { setNewTokenUserId(userId); if (userId) { @@ -176,7 +67,13 @@ export function ApiTab() { } }; - if (loading) { + const handleCancel = () => { + api.resetForm(); + setNewTokenUserId(''); + setNewTokenRole(''); + }; + + if (api.loading) { return (
@@ -197,14 +94,14 @@ export function ApiTab() {
{/* Error display */} - {error && ( + {api.error && (
- {error} + {api.error}
)} {/* Newly created token banner */} - {createdToken && ( + {api.createdToken && (
@@ -216,18 +113,18 @@ export function ApiTab() {

- {createdToken} + {api.createdToken}