mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 21:00:09 +00:00
Fix token UI success handling, fetch error surfacing, and docs key stability
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user