Fix token UI success handling, fetch error surfacing, and docs key stability

This commit is contained in:
Michael Borohovski
2026-03-04 16:37:00 -08:00
parent a5e7af1a53
commit 81813dc625
4 changed files with 31 additions and 13 deletions
@@ -47,9 +47,9 @@ export function ApiTab() {
const extraBody: Record<string, string> = {}; const extraBody: Record<string, string> = {};
if (newTokenUserId) extraBody.userId = newTokenUserId; if (newTokenUserId) extraBody.userId = newTokenUserId;
if (newTokenRole) extraBody.role = newTokenRole; if (newTokenRole) extraBody.role = newTokenRole;
await api.handleCreate(extraBody); const created = await api.handleCreate(extraBody);
// Reset admin-specific fields on success // Reset admin-specific fields only when create succeeds
if (!api.error) { if (created) {
setNewTokenUserId(''); setNewTokenUserId('');
setNewTokenRole(''); setNewTokenRole('');
} }
@@ -123,10 +123,12 @@ export function ApiTab() {
</button> </button>
</div> </div>
</div> </div>
<button <button
onClick={api.dismissCreatedToken} type="button"
className="flex-shrink-0 text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-200" aria-label="Dismiss token banner"
> onClick={api.dismissCreatedToken}
className="flex-shrink-0 text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-200"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
+1 -1
View File
@@ -120,7 +120,7 @@ export default function ApiDocsPage() {
<div className="space-y-4"> <div className="space-y-4">
{API_TOKEN_ENDPOINT_DOCS.map((endpoint) => ( {API_TOKEN_ENDPOINT_DOCS.map((endpoint) => (
<EndpointCard <EndpointCard
key={endpoint.path} key={`${endpoint.method}:${endpoint.path}`}
endpoint={endpoint} endpoint={endpoint}
token={token} token={token}
useSession={useSession} useSession={useSession}
@@ -63,6 +63,8 @@ export function ApiTokensSection() {
</div> </div>
</div> </div>
<button <button
type="button"
aria-label="Dismiss token banner"
onClick={api.dismissCreatedToken} onClick={api.dismissCreatedToken}
className="flex-shrink-0 text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-200" className="flex-shrink-0 text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-200"
> >
+19 -5
View File
@@ -39,7 +39,7 @@ export interface UseApiTokensReturn<T extends ApiToken = ApiToken> {
confirmRevokeId: string | null; confirmRevokeId: string | null;
setConfirmRevokeId: (id: string | null) => void; setConfirmRevokeId: (id: string | null) => void;
fetchTokens: () => Promise<void>; fetchTokens: () => Promise<void>;
handleCreate: (extraBody?: Partial<CreateTokenBody>) => Promise<void>; handleCreate: (extraBody?: Partial<CreateTokenBody>) => Promise<boolean>;
handleDeleteConfirmed: () => Promise<void>; handleDeleteConfirmed: () => Promise<void>;
handleCopy: () => Promise<void>; handleCopy: () => Promise<void>;
dismissCreatedToken: () => void; dismissCreatedToken: () => void;
@@ -69,10 +69,21 @@ export function useApiTokens<T extends ApiToken = ApiToken>(
const fetchTokens = useCallback(async () => { const fetchTokens = useCallback(async () => {
try { try {
const response = await fetchWithAuth(config.basePath); const response = await fetchWithAuth(config.basePath);
if (response.ok) { if (!response.ok) {
const data = await response.json(); let message = 'Failed to load API tokens';
setTokens(data.tokens); try {
const data = await response.json();
message = data.error || message;
} catch {
// Keep default message when response body is not JSON
}
setError(message);
return;
} }
const data = await response.json();
setTokens(data.tokens);
setError(null);
} catch { } catch {
setError('Failed to load API tokens'); setError('Failed to load API tokens');
} finally { } finally {
@@ -98,7 +109,7 @@ export function useApiTokens<T extends ApiToken = ApiToken>(
const handleCreate = async (extraBody?: Partial<CreateTokenBody>) => { const handleCreate = async (extraBody?: Partial<CreateTokenBody>) => {
if (!newTokenName.trim()) { if (!newTokenName.trim()) {
setError('Token name is required'); setError('Token name is required');
return; return false;
} }
setCreating(true); setCreating(true);
@@ -124,12 +135,15 @@ export function useApiTokens<T extends ApiToken = ApiToken>(
setNewTokenExpiry('never'); setNewTokenExpiry('never');
setShowCreateForm(false); setShowCreateForm(false);
await fetchTokens(); await fetchTokens();
return true;
} else { } else {
const data = await response.json(); const data = await response.json();
setError(data.error || 'Failed to create token'); setError(data.error || 'Failed to create token');
return false;
} }
} catch { } catch {
setError('Failed to create token'); setError('Failed to create token');
return false;
} finally { } finally {
setCreating(false); setCreating(false);
} }