mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 12:28:22 +00:00
Merge pull request #657 from ashen0x/ui/api-error-messages
fix: surface backend error details instead of raw axios strings
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { getApiErrorMessage } from '@/lib/api/errors';
|
||||
import {
|
||||
AdminService,
|
||||
ProviderModels,
|
||||
@@ -139,7 +140,7 @@ export default function ProvidersPage() {
|
||||
resetForm();
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Failed to create provider: ${error.message}`);
|
||||
toast.error(getApiErrorMessage(error, 'Failed to create provider'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -153,7 +154,7 @@ export default function ProvidersPage() {
|
||||
toast.success('Provider updated successfully');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Failed to update provider: ${error.message}`);
|
||||
toast.error(getApiErrorMessage(error, 'Failed to update provider'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -164,7 +165,7 @@ export default function ProvidersPage() {
|
||||
toast.success('Provider deleted successfully');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Failed to delete provider: ${error.message}`);
|
||||
toast.error(getApiErrorMessage(error, 'Failed to delete provider'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -183,7 +184,7 @@ export default function ProvidersPage() {
|
||||
toast.success('Model deleted successfully');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Failed to delete model: ${error.message}`);
|
||||
toast.error(getApiErrorMessage(error, 'Failed to delete model'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -205,9 +206,7 @@ export default function ProvidersPage() {
|
||||
toast.success('Account created, but no API key returned.');
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'Unknown error';
|
||||
toast.error(`Failed to create account: ${errorMessage}`);
|
||||
toast.error(getApiErrorMessage(error, 'Failed to create account'));
|
||||
} finally {
|
||||
setIsCreatingAccount(false);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { RefreshCw, AlertCircle, Wallet, User, Coins } from 'lucide-react';
|
||||
import { WalletService, BalanceDetail } from '@/lib/api/services/wallet';
|
||||
import { getApiErrorMessage } from '@/lib/api/errors';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -191,7 +192,8 @@ export function DetailedWalletBalance({
|
||||
<Alert variant='destructive'>
|
||||
<AlertCircle className='h-5 w-5' />
|
||||
<AlertDescription>
|
||||
Error loading balance: {(error as Error).message}
|
||||
Error loading balance:{' '}
|
||||
{getApiErrorMessage(error, 'Request failed')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { AlertCircle, Copy, Trash2, Check } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { getApiErrorMessage } from '@/lib/api/errors';
|
||||
|
||||
function formatTs(ts: number | null): string {
|
||||
if (!ts) return '—';
|
||||
@@ -44,9 +45,7 @@ export function CliTokensSettings(): React.ReactElement {
|
||||
const data = await AdminService.listCliTokens();
|
||||
setTokens(data);
|
||||
} catch (err: unknown) {
|
||||
const message =
|
||||
err instanceof Error ? err.message : 'Failed to load tokens';
|
||||
setError(message);
|
||||
setError(getApiErrorMessage(err, 'Failed to load tokens'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -79,9 +78,7 @@ export function CliTokensSettings(): React.ReactElement {
|
||||
await loadTokens();
|
||||
toast.success('Token created. Copy it now — it will not be shown again.');
|
||||
} catch (err: unknown) {
|
||||
const message =
|
||||
err instanceof Error ? err.message : 'Failed to create token';
|
||||
toast.error(message);
|
||||
toast.error(getApiErrorMessage(err, 'Failed to create token'));
|
||||
} finally {
|
||||
setCreating(false);
|
||||
}
|
||||
@@ -98,9 +95,7 @@ export function CliTokensSettings(): React.ReactElement {
|
||||
await loadTokens();
|
||||
toast.success('Token revoked');
|
||||
} catch (err: unknown) {
|
||||
const message =
|
||||
err instanceof Error ? err.message : 'Failed to revoke token';
|
||||
toast.error(message);
|
||||
toast.error(getApiErrorMessage(err, 'Failed to revoke token'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import { AdminService } from '@/lib/api/services/admin';
|
||||
import { getApiErrorMessage } from '@/lib/api/errors';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -169,7 +170,8 @@ export function TemporaryBalances({
|
||||
<Alert variant='destructive'>
|
||||
<AlertCircle className='h-5 w-5' />
|
||||
<AlertDescription>
|
||||
Error loading API keys: {(error as Error).message}
|
||||
Error loading API keys:{' '}
|
||||
{getApiErrorMessage(error, 'Request failed')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { isAxiosError } from 'axios';
|
||||
|
||||
/**
|
||||
* Human-readable message for a failed API call. Prefers the backend's
|
||||
* `detail` field; never surfaces raw axios strings like
|
||||
* "Request failed with status code 500".
|
||||
*/
|
||||
export function getApiErrorMessage(
|
||||
error: unknown,
|
||||
fallback = 'Something went wrong'
|
||||
): string {
|
||||
if (isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
return 'Cannot reach the node. Is it running?';
|
||||
}
|
||||
const detail: unknown = error.response.data?.detail;
|
||||
if (typeof detail === 'string' && detail) {
|
||||
return detail;
|
||||
}
|
||||
return `${fallback} (HTTP ${error.response.status})`;
|
||||
}
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
Reference in New Issue
Block a user