fix(ui): extract meaningful error messages from API responses
Introduces ApiError class in client.ts that parses Spring Boot error bodies to extract human-readable messages (message, error, detail fields). Adds errorMessage() helper used by all toast descriptions instead of raw String(err) which dumped JSON blobs to the user. Affected: all 10 page components that display error toasts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
9
ui/src/pages/vendor/CertificatesPage.tsx
vendored
9
ui/src/pages/vendor/CertificatesPage.tsx
vendored
@@ -1,4 +1,5 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { errorMessage } from '../../api/client';
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
@@ -163,7 +164,7 @@ export function CertificatesPage() {
|
||||
toast({ title: 'Validation failed', description: result.errors.join(', '), variant: 'error' });
|
||||
}
|
||||
} catch (err) {
|
||||
toast({ title: 'Upload failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Upload failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +173,7 @@ export function CertificatesPage() {
|
||||
await activateMutation.mutateAsync();
|
||||
toast({ title: 'Certificate activated', variant: 'success' });
|
||||
} catch (err) {
|
||||
toast({ title: 'Activation failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Activation failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +182,7 @@ export function CertificatesPage() {
|
||||
await restoreMutation.mutateAsync();
|
||||
toast({ title: 'Certificate restored from archive', variant: 'success' });
|
||||
} catch (err) {
|
||||
toast({ title: 'Restore failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Restore failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +191,7 @@ export function CertificatesPage() {
|
||||
await discardMutation.mutateAsync();
|
||||
toast({ title: 'Staged certificate discarded', variant: 'success' });
|
||||
} catch (err) {
|
||||
toast({ title: 'Discard failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Discard failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
ui/src/pages/vendor/CreateTenantPage.tsx
vendored
3
ui/src/pages/vendor/CreateTenantPage.tsx
vendored
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Button, Card, FormField, Input, useToast } from '@cameleer/design-system';
|
||||
import { useCreateTenant } from '../../api/vendor-hooks';
|
||||
import { errorMessage } from '../../api/client';
|
||||
import { toSlug } from '../../utils/slug';
|
||||
|
||||
const TIERS = ['STARTER', 'TEAM', 'BUSINESS', 'ENTERPRISE'];
|
||||
@@ -35,7 +36,7 @@ export function CreateTenantPage() {
|
||||
toast({ title: 'Tenant created — provisioning in progress', variant: 'success' });
|
||||
navigate(`/vendor/tenants/${result.id}`);
|
||||
} catch (err) {
|
||||
toast({ title: 'Failed to create tenant', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Failed to create tenant', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
ui/src/pages/vendor/EmailConfigPage.tsx
vendored
9
ui/src/pages/vendor/EmailConfigPage.tsx
vendored
@@ -9,6 +9,7 @@ import {
|
||||
useToast,
|
||||
} from '@cameleer/design-system';
|
||||
import { Send, Trash2, Save, Power } from 'lucide-react';
|
||||
import { errorMessage } from '../../api/client';
|
||||
import {
|
||||
useEmailConnector,
|
||||
useSaveEmailConnector,
|
||||
@@ -70,7 +71,7 @@ export function EmailConfigPage() {
|
||||
setEditing(false);
|
||||
setPassword('');
|
||||
} catch (err) {
|
||||
toast({ title: 'Failed to save', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Failed to save', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ export function EmailConfigPage() {
|
||||
setConfirmDelete(false);
|
||||
setEditing(false);
|
||||
} catch (err) {
|
||||
toast({ title: 'Failed to delete', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Failed to delete', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ export function EmailConfigPage() {
|
||||
toast({ title: 'Test failed', description: result.message, variant: 'error' });
|
||||
}
|
||||
} catch (err) {
|
||||
toast({ title: 'Test failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Test failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +113,7 @@ export function EmailConfigPage() {
|
||||
variant: 'success',
|
||||
});
|
||||
} catch (err) {
|
||||
toast({ title: 'Failed to toggle registration', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Failed to toggle registration', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
ui/src/pages/vendor/LicenseVerifyPage.tsx
vendored
3
ui/src/pages/vendor/LicenseVerifyPage.tsx
vendored
@@ -7,6 +7,7 @@ import {
|
||||
} from '@cameleer/design-system';
|
||||
import { Clipboard, Search, ShieldCheck } from 'lucide-react';
|
||||
import { useVerifyLicense, usePublicKey } from '../../api/vendor-hooks';
|
||||
import { errorMessage } from '../../api/client';
|
||||
import styles from '../../styles/platform.module.css';
|
||||
import type { VerifyLicenseResponse } from '../../types/api';
|
||||
|
||||
@@ -49,7 +50,7 @@ export function LicenseVerifyPage() {
|
||||
const res = await verifyLicense.mutateAsync(token.trim());
|
||||
setResult(res);
|
||||
} catch (err) {
|
||||
toast({ title: 'Verification failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Verification failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
ui/src/pages/vendor/TenantDetailPage.tsx
vendored
11
ui/src/pages/vendor/TenantDetailPage.tsx
vendored
@@ -20,6 +20,7 @@ import {
|
||||
useRestartServer,
|
||||
useUpgradeServer,
|
||||
} from '../../api/vendor-hooks';
|
||||
import { errorMessage } from '../../api/client';
|
||||
import { ServerStatusBadge } from '../../components/ServerStatusBadge';
|
||||
import { tierColor } from '../../utils/tier';
|
||||
import styles from '../../styles/platform.module.css';
|
||||
@@ -155,7 +156,7 @@ export function TenantDetailPage() {
|
||||
variant: 'success',
|
||||
});
|
||||
} catch (err) {
|
||||
toast({ title: 'Minting failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Minting failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +201,7 @@ export function TenantDetailPage() {
|
||||
toast({ title: 'Tenant suspended', variant: 'warning' });
|
||||
}
|
||||
} catch (err) {
|
||||
toast({ title: 'Action failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Action failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +211,7 @@ export function TenantDetailPage() {
|
||||
await restartServer.mutateAsync(id);
|
||||
toast({ title: 'Server restarted', variant: 'success' });
|
||||
} catch (err) {
|
||||
toast({ title: 'Restart failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Restart failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +221,7 @@ export function TenantDetailPage() {
|
||||
await upgradeServer.mutateAsync(id);
|
||||
toast({ title: 'Server upgrade started — pulling latest images', variant: 'success' });
|
||||
} catch (err) {
|
||||
toast({ title: 'Upgrade failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Upgrade failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +232,7 @@ export function TenantDetailPage() {
|
||||
toast({ title: 'Tenant deleted', variant: 'success' });
|
||||
navigate('/vendor/tenants');
|
||||
} catch (err) {
|
||||
toast({ title: 'Delete failed', description: String(err), variant: 'error' });
|
||||
toast({ title: 'Delete failed', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user