Skip to content

Commit 7c9754b

Browse files
Copilot0xrinegade
andcommitted
Complete error handling improvements and documentation updates
Co-authored-by: 0xrinegade <[email protected]>
1 parent d7408bd commit 7c9754b

File tree

3 files changed

+216
-20
lines changed

3 files changed

+216
-20
lines changed

SWIG_WALLET_MIGRATION.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,53 @@
1919
- Clean up legacy code paths
2020
- **Timeline**: Next major release
2121

22+
## New Features & Improvements
23+
24+
### Enhanced Error Handling System
25+
The new Swig wallet integration includes a categorized error handling system:
26+
27+
#### Error Categories
28+
- **Critical Errors**: Authentication failures, API issues (persistent toasts)
29+
- **System Errors**: Network issues, connection problems (actionable toasts)
30+
- **Informational Errors**: Form validation, user input (inline or brief toasts)
31+
- **Success Messages**: Brief confirmation toasts
32+
33+
#### Usage Examples
34+
```javascript
35+
const { toast } = useToast();
36+
37+
// Critical errors - persistent with retry actions
38+
toast.criticalError('Authentication failed', {
39+
action: <button onClick={retry}>Retry</button>
40+
});
41+
42+
// System errors - connection issues with fallbacks
43+
toast.systemError('Connection lost', {
44+
action: <button onClick={reconnect}>Reconnect</button>
45+
});
46+
47+
// Success messages - brief confirmations
48+
toast.success('Wallet connected successfully');
49+
```
50+
51+
### Improved Reconnection Logic
52+
- **Exponential backoff with jitter** prevents thundering herd problems
53+
- **Comprehensive timeout cleanup** prevents memory leaks
54+
- **Progress tracking UI** shows reconnection attempts to users
55+
- **Cancellation support** allows users to stop reconnection
56+
57+
### Enhanced Popup Handling
58+
- **Better popup blocker detection** including mobile browsers
59+
- **Fallback options** for blocked popups (same-tab navigation)
60+
- **Sequential popup management** reduces blocker issues
61+
- **User-friendly error messages** with actionable buttons
62+
63+
### Accessibility Improvements
64+
- **Focus trapping** in modals for keyboard navigation
65+
- **ARIA labels** for screen readers
66+
- **Escape key support** for modal dismissal
67+
- **Proper focus restoration** after modal close
68+
2269
## Migration Steps
2370

2471
### 1. Update Hook Usage

src/contexts/SwigWalletProvider.js

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { useToast } from '../hooks/useToast';
2121
import { ToastContainer } from '../components/Toast';
2222
import { ReconnectionModal } from '../components/ReconnectionModal';
2323
import { SVM_NETWORKS, getNetworkConfig, getDefaultNetworkConfig } from '../config/networks';
24+
import { ERROR_CATEGORIES } from '../hooks/useToast';
2425

2526
// Maximum number of reconnection attempts
2627
const MAX_RECONNECT_ATTEMPTS = 3;
@@ -196,12 +197,23 @@ const SwigWalletProvider = ({ children }) => {
196197
setPublicKey(new PublicKey(selectedWallet.address));
197198
setConnected(true);
198199
setConnectionState('connected');
199-
toast.success('Wallet connected successfully');
200+
toast.success('Wallet connected successfully', {
201+
category: ERROR_CATEGORIES.SUCCESS
202+
});
200203
} else {
201204
const errorMsg = `No ${storedWalletType} wallet found`;
202205
setError(errorMsg);
203206
setConnectionState('error');
204-
toast.error(errorMsg);
207+
toast.criticalError(errorMsg, {
208+
action: (
209+
<button
210+
onClick={() => window.location.reload()}
211+
className="mt-2 px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600"
212+
>
213+
Retry
214+
</button>
215+
)
216+
});
205217
}
206218
} else {
207219
setConnected(false);
@@ -212,7 +224,22 @@ const SwigWalletProvider = ({ children }) => {
212224
const errorMsg = err.message || 'Authentication check failed';
213225
setError(errorMsg);
214226
setConnectionState('error');
215-
toast.error(`Connection failed: ${errorMsg}`);
227+
228+
// Categorize error based on type
229+
if (err.message?.includes('network') || err.message?.includes('connection')) {
230+
toast.systemError(`Connection failed: ${errorMsg}`, {
231+
action: (
232+
<button
233+
onClick={() => checkAuthentication()}
234+
className="mt-2 px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600"
235+
>
236+
Retry Connection
237+
</button>
238+
)
239+
});
240+
} else {
241+
toast.criticalError(`Authentication failed: ${errorMsg}`);
242+
}
216243
} finally {
217244
setConnecting(false);
218245
}
@@ -243,8 +270,10 @@ const SwigWalletProvider = ({ children }) => {
243270

244271
const errorMsg = `Popup blocked. Please allow popups for this site and try again.`;
245272

246-
toast.error(errorMsg, {
273+
toast.systemError(errorMsg, {
274+
category: ERROR_CATEGORIES.SYSTEM,
247275
duration: 15000, // Longer duration for instructions
276+
persistent: true,
248277
action: (
249278
<div className="mt-3 space-y-2">
250279
<button
@@ -372,7 +401,22 @@ const SwigWalletProvider = ({ children }) => {
372401
} else {
373402
const errorMsg = err.message || 'Authentication failed';
374403
setError(errorMsg);
375-
toast.error(`Login failed: ${errorMsg}`);
404+
405+
// Categorize authentication errors
406+
if (errorMsg.includes('network') || errorMsg.includes('timeout')) {
407+
toast.systemError(`Login failed: ${errorMsg}`, {
408+
action: (
409+
<button
410+
onClick={() => authenticate(method)}
411+
className="mt-2 px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600"
412+
>
413+
Retry Login
414+
</button>
415+
)
416+
});
417+
} else {
418+
toast.criticalError(`Login failed: ${errorMsg}`);
419+
}
376420
}
377421
setConnectionState('error');
378422
} finally {
@@ -404,12 +448,14 @@ const SwigWalletProvider = ({ children }) => {
404448
setError(null);
405449
setConnectionState('disconnected');
406450

407-
toast.success('Wallet disconnected');
451+
toast.success('Wallet disconnected', {
452+
category: ERROR_CATEGORIES.SUCCESS
453+
});
408454
} catch (err) {
409455
console.error('[SwigWalletProvider] Disconnect failed:', err);
410456
const errorMsg = err.message || 'Disconnect failed';
411457
setError(errorMsg);
412-
toast.error(`Disconnect failed: ${errorMsg}`);
458+
toast.criticalError(`Disconnect failed: ${errorMsg}`);
413459
}
414460
}, [toast]);
415461

@@ -426,12 +472,14 @@ const SwigWalletProvider = ({ children }) => {
426472
// This is a simplified version - full implementation would use createSwigAccount utilities
427473

428474
console.log('[SwigWalletProvider] Swig wallet setup completed');
429-
toast.success('Swig wallet setup completed');
475+
toast.success('Swig wallet setup completed', {
476+
category: ERROR_CATEGORIES.SUCCESS
477+
});
430478
} catch (err) {
431479
console.error('[SwigWalletProvider] Swig wallet setup failed:', err);
432480
const errorMsg = err.message || 'Swig wallet setup failed';
433481
setError(errorMsg);
434-
toast.error(`Setup failed: ${errorMsg}`);
482+
toast.criticalError(`Setup failed: ${errorMsg}`);
435483
} finally {
436484
setIsSettingUp(false);
437485
}

src/hooks/useToast.js

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,63 @@
11
/**
22
* Toast hook for managing toast notifications
3+
*
4+
* Provides categorized error handling:
5+
* - Critical errors: Show as persistent toasts (auth failures, network issues)
6+
* - Informational errors: Show inline or as brief toasts (form validation)
7+
* - System errors: Show as urgent toasts with actions (connection lost)
38
*/
49

510
import { useState, useCallback } from 'react';
611

712
let toastId = 0;
813

914
/**
10-
* Hook for managing toast notifications
15+
* Error categories for proper UI placement
16+
*/
17+
export const ERROR_CATEGORIES = {
18+
CRITICAL: 'critical', // Show as persistent toasts (auth, network)
19+
INFORMATIONAL: 'info', // Show inline or brief toasts (validation)
20+
SYSTEM: 'system', // Show as urgent toasts with actions (connection)
21+
SUCCESS: 'success', // Show as brief success toasts
22+
WARNING: 'warning' // Show as warning toasts
23+
};
24+
25+
/**
26+
* Hook for managing categorized toast notifications
1127
* @returns {Object} Toast management functions and state
1228
*/
1329
export const useToast = () => {
1430
const [toasts, setToasts] = useState([]);
1531

16-
const addToast = useCallback(({ message, type = 'info', duration = 5000, action = null }) => {
32+
const addToast = useCallback(({
33+
message,
34+
type = 'info',
35+
category = ERROR_CATEGORIES.INFORMATIONAL,
36+
duration = 5000,
37+
action = null,
38+
persistent = false
39+
}) => {
1740
const id = ++toastId;
41+
42+
// Adjust duration based on category
43+
let adjustedDuration = duration;
44+
if (category === ERROR_CATEGORIES.CRITICAL) {
45+
adjustedDuration = persistent ? 0 : 10000; // 10s or persistent
46+
} else if (category === ERROR_CATEGORIES.SYSTEM) {
47+
adjustedDuration = persistent ? 0 : 15000; // 15s or persistent
48+
} else if (category === ERROR_CATEGORIES.SUCCESS) {
49+
adjustedDuration = 3000; // Brief success messages
50+
}
51+
1852
const toast = {
1953
id,
2054
message,
2155
type,
22-
duration,
56+
category,
57+
duration: adjustedDuration,
2358
action,
24-
timestamp: Date.now()
59+
timestamp: Date.now(),
60+
persistent
2561
};
2662

2763
setToasts(prev => [...prev, toast]);
@@ -36,33 +72,98 @@ export const useToast = () => {
3672
setToasts([]);
3773
}, []);
3874

39-
// Convenience methods for different toast types
75+
const clearByCategory = useCallback((category) => {
76+
setToasts(prev => prev.filter(toast => toast.category !== category));
77+
}, []);
78+
79+
// Convenience methods for different toast types with proper categorization
4080
const success = useCallback((message, options = {}) => {
41-
return addToast({ message, type: 'success', ...options });
81+
return addToast({
82+
message,
83+
type: 'success',
84+
category: ERROR_CATEGORIES.SUCCESS,
85+
...options
86+
});
4287
}, [addToast]);
4388

4489
const error = useCallback((message, options = {}) => {
45-
return addToast({ message, type: 'error', duration: 8000, ...options });
90+
return addToast({
91+
message,
92+
type: 'error',
93+
category: ERROR_CATEGORIES.CRITICAL,
94+
duration: 8000,
95+
...options
96+
});
4697
}, [addToast]);
4798

4899
const warning = useCallback((message, options = {}) => {
49-
return addToast({ message, type: 'warning', duration: 6000, ...options });
100+
return addToast({
101+
message,
102+
type: 'warning',
103+
category: ERROR_CATEGORIES.WARNING,
104+
duration: 6000,
105+
...options
106+
});
50107
}, [addToast]);
51108

52109
const info = useCallback((message, options = {}) => {
53-
return addToast({ message, type: 'info', ...options });
110+
return addToast({
111+
message,
112+
type: 'info',
113+
category: ERROR_CATEGORIES.INFORMATIONAL,
114+
...options
115+
});
116+
}, [addToast]);
117+
118+
// Specialized methods for different error categories
119+
const criticalError = useCallback((message, options = {}) => {
120+
return addToast({
121+
message,
122+
type: 'error',
123+
category: ERROR_CATEGORIES.CRITICAL,
124+
duration: 10000,
125+
persistent: true,
126+
...options
127+
});
128+
}, [addToast]);
129+
130+
const systemError = useCallback((message, options = {}) => {
131+
return addToast({
132+
message,
133+
type: 'error',
134+
category: ERROR_CATEGORIES.SYSTEM,
135+
duration: 15000,
136+
...options
137+
});
54138
}, [addToast]);
55139

140+
const inlineError = useCallback((message, options = {}) => {
141+
// For inline errors, we provide the message but don't show toast
142+
// Components can use this for form validation, etc.
143+
return {
144+
message,
145+
type: 'error',
146+
category: ERROR_CATEGORIES.INFORMATIONAL,
147+
inline: true,
148+
...options
149+
};
150+
}, []);
151+
56152
return {
57153
toasts,
58154
addToast,
59155
removeToast,
60156
clearAllToasts,
157+
clearByCategory,
61158
success,
62159
error,
63160
warning,
64-
info
161+
info,
162+
criticalError,
163+
systemError,
164+
inlineError,
165+
ERROR_CATEGORIES
65166
};
66167
};
67168

68-
export default useToast;
169+
export default useToast;

0 commit comments

Comments
 (0)