mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-01 19:12:57 +02:00
Add better error bubble-up
This commit is contained in:
@@ -617,16 +617,45 @@ The app uses Sonner for toast notifications via a custom wrapper at `components/
|
||||
```typescript
|
||||
import { toast } from './components/ui/sonner';
|
||||
|
||||
// Success toast
|
||||
toast.success('Operation completed', { description: 'Details here' });
|
||||
// Success toast (use sparingly - only for significant/destructive actions)
|
||||
toast.success('Channel deleted');
|
||||
|
||||
// Error toast (muted red styling for readability)
|
||||
toast.error('Operation failed', { description: 'Error details' });
|
||||
// Error toast with details
|
||||
toast.error('Failed to send message', {
|
||||
description: err instanceof Error ? err.message : 'Check radio connection',
|
||||
});
|
||||
```
|
||||
|
||||
Toasts are automatically shown for:
|
||||
### Error Handling Pattern
|
||||
|
||||
All async operations that can fail should show error toasts. Keep console.error for debugging:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await api.someOperation();
|
||||
} catch (err) {
|
||||
console.error('Failed to do X:', err);
|
||||
toast.error('Failed to do X', {
|
||||
description: err instanceof Error ? err.message : 'Check radio connection',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Where Toasts Are Used
|
||||
|
||||
**Error toasts** (shown when operations fail):
|
||||
- `App.tsx`: Advertisement, channel delete, contact delete
|
||||
- `useConversationMessages.ts`: Message loading (initial and pagination)
|
||||
- `MessageInput.tsx`: Message send, telemetry request
|
||||
- `CrackerPanel.tsx`: Channel save after cracking, WebGPU unavailable
|
||||
- `StatusBar.tsx`: Manual reconnection failure
|
||||
- `useWebSocket.ts`: Backend errors via WebSocket `error` events
|
||||
|
||||
**Success toasts** (used sparingly for significant actions):
|
||||
- Radio connection/disconnection status changes
|
||||
- Backend errors received via WebSocket `error` events
|
||||
- Manual reconnection success/failure
|
||||
- Manual reconnection success
|
||||
- Advertisement sent, channel/contact deleted (confirmation of intentional actions)
|
||||
|
||||
**Avoid success toasts** for routine operations like sending messages - only show errors.
|
||||
|
||||
The `<Toaster />` component is rendered in `App.tsx` with `position="top-right"`.
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
frontend/dist/index.html
vendored
2
frontend/dist/index.html
vendored
@@ -13,7 +13,7 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<script type="module" crossorigin src="/assets/index-CBdmGStv.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CTjJAYeA.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DaLCXB8p.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -352,8 +352,12 @@ export function App() {
|
||||
const handleAdvertise = useCallback(async () => {
|
||||
try {
|
||||
await api.sendAdvertisement(true);
|
||||
toast.success('Advertisement sent');
|
||||
} catch (err) {
|
||||
console.error('Failed to send advertisement:', err);
|
||||
toast.error('Failed to send advertisement', {
|
||||
description: err instanceof Error ? err.message : 'Check radio connection',
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -375,8 +379,12 @@ export function App() {
|
||||
await api.deleteChannel(key);
|
||||
setChannels((prev) => prev.filter((c) => c.key !== key));
|
||||
setActiveConversation(null);
|
||||
toast.success('Channel deleted');
|
||||
} catch (err) {
|
||||
console.error('Failed to delete channel:', err);
|
||||
toast.error('Failed to delete channel', {
|
||||
description: err instanceof Error ? err.message : undefined,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -387,8 +395,12 @@ export function App() {
|
||||
await api.deleteContact(publicKey);
|
||||
setContacts((prev) => prev.filter((c) => c.public_key !== publicKey));
|
||||
setActiveConversation(null);
|
||||
toast.success('Contact deleted');
|
||||
} catch (err) {
|
||||
console.error('Failed to delete contact:', err);
|
||||
toast.error('Failed to delete contact', {
|
||||
description: err instanceof Error ? err.message : undefined,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ENGLISH_WORDLIST } from 'meshcore-hashtag-cracker/wordlist';
|
||||
import NoSleep from 'nosleep.js';
|
||||
import type { RawPacket, Channel } from '../types';
|
||||
import { api } from '../api';
|
||||
import { toast } from './ui/sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
/**
|
||||
@@ -338,6 +339,9 @@ export function CrackerPanel({ packets, channels, onChannelCreate, onRunningChan
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to create channel or decrypt historical:', err);
|
||||
toast.error('Failed to save cracked channel', {
|
||||
description: err instanceof Error ? err.message : 'Channel discovered but could not be saved',
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -386,7 +390,9 @@ export function CrackerPanel({ packets, channels, onChannelCreate, onRunningChan
|
||||
// Start/stop handlers
|
||||
const handleStart = () => {
|
||||
if (!gpuAvailable) {
|
||||
alert('WebGPU is not available in your browser. Please use Chrome 113+ or Edge 113+.');
|
||||
toast.error('WebGPU not available', {
|
||||
description: 'Cracking requires Chrome 113+ or Edge 113+ with WebGPU support.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
setIsRunning(true);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useCallback, useImperativeHandle, forwardRef, useRef, useMemo, type FormEvent, type KeyboardEvent } from 'react';
|
||||
import { Input } from './ui/input';
|
||||
import { Button } from './ui/button';
|
||||
import { toast } from './ui/sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// MeshCore message size limits (empirically determined from LoRa packet constraints)
|
||||
@@ -103,6 +104,9 @@ export const MessageInput = forwardRef<MessageInputHandle, MessageInputProps>(
|
||||
setText('');
|
||||
} catch (err) {
|
||||
console.error('Failed to request telemetry:', err);
|
||||
toast.error('Failed to request telemetry', {
|
||||
description: err instanceof Error ? err.message : 'Check radio connection',
|
||||
});
|
||||
return;
|
||||
} finally {
|
||||
setSending(false);
|
||||
@@ -117,6 +121,9 @@ export const MessageInput = forwardRef<MessageInputHandle, MessageInputProps>(
|
||||
setText('');
|
||||
} catch (err) {
|
||||
console.error('Failed to send message:', err);
|
||||
toast.error('Failed to send message', {
|
||||
description: err instanceof Error ? err.message : 'Check radio connection',
|
||||
});
|
||||
return;
|
||||
} finally {
|
||||
setSending(false);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { toast } from '../components/ui/sonner';
|
||||
import { api } from '../api';
|
||||
import type { Conversation, Message } from '../types';
|
||||
|
||||
@@ -61,6 +62,9 @@ export function useConversationMessages(
|
||||
setHasOlderMessages(data.length >= MESSAGE_PAGE_SIZE);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch messages:', err);
|
||||
toast.error('Failed to load messages', {
|
||||
description: err instanceof Error ? err.message : 'Check your connection',
|
||||
});
|
||||
} finally {
|
||||
if (showLoading) {
|
||||
setMessagesLoading(false);
|
||||
@@ -93,6 +97,9 @@ export function useConversationMessages(
|
||||
setHasOlderMessages(data.length >= MESSAGE_PAGE_SIZE);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch older messages:', err);
|
||||
toast.error('Failed to load older messages', {
|
||||
description: err instanceof Error ? err.message : 'Check your connection',
|
||||
});
|
||||
} finally {
|
||||
setLoadingOlder(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user