🎯 Objective
Create a centralized toast/notification system for the frontend to replace the inconsistent inline error/success message patterns scattered across components.
📁 Files to Create
| Action |
File Path |
Description |
| Create |
frontend/src/context/ToastContext.tsx |
Toast provider with queue management |
| Create |
frontend/src/hooks/useToast.ts |
Hook for components to trigger toasts |
| Create |
frontend/src/components/common/Toast.tsx |
Toast UI component with animations |
| Create |
frontend/src/components/common/Toast.css |
Toast styles and animations |
📁 Files to Modify
| Action |
File Path |
Description |
| Modify |
frontend/src/App.tsx |
Wrap app with ToastProvider |
| Modify |
frontend/src/components/agents/TaskSubmissionForm.tsx |
Replace inline error div with useToast |
| Modify |
frontend/src/pages/dashboard.tsx |
Add error toasts for failed data fetches |
| Modify |
frontend/src/components/dashboard/RecentTasksTable.tsx |
Replace silent console.error with toast |
| Modify |
frontend/src/services/api.ts |
Trigger toast on 503 retry and 401 disconnect |
🔍 Current Inconsistent Patterns
| Component |
Current Pattern |
Problem |
TaskSubmissionForm.tsx |
Fixed-position inline error div |
Duplicated per component |
DashboardPage.tsx |
console.error() only |
No user feedback |
RecentTasksTable.tsx |
setTasks([]) + console.error |
Silent failure |
useTaskMonitor.ts |
Falls back to mock data |
Hides real errors |
api.ts |
503 retry with no user feedback |
User unaware of retries |
✅ Expected Implementation
1. Create ToastContext.tsx
import { createContext, useContext, useState, useCallback } from 'react';
type ToastType = 'success' | 'error' | 'warning' | 'info';
interface Toast {
id: string;
message: string;
type: ToastType;
duration?: number;
}
interface ToastContextValue {
toasts: Toast[];
showToast: (message: string, type?: ToastType, duration?: number) => void;
dismissToast: (id: string) => void;
}
const ToastContext = createContext<ToastContextValue | null>(null);
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const showToast = useCallback((message: string, type: ToastType = 'info', duration = 5000) => {
const id = Math.random().toString(36).slice(2);
setToasts(prev => [...prev, { id, message, type, duration }]);
if (duration > 0) {
setTimeout(() => dismissToast(id), duration);
}
}, []);
const dismissToast = useCallback((id: string) => {
setToasts(prev => prev.filter(t => t.id !== id));
}, []);
return (
<ToastContext.Provider value={{ toasts, showToast, dismissToast }}>
{children}
<ToastContainer toasts={toasts} onDismiss={dismissToast} />
</ToastContext.Provider>
);
}
export function useToast() {
const context = useContext(ToastContext);
if (!context) throw new Error('useToast must be used within ToastProvider');
return context;
}
2. Create Toast.tsx
function ToastContainer({ toasts, onDismiss }: { toasts: Toast[]; onDismiss: (id: string) => void }) {
return (
<div className="toast-container" role="alert" aria-live="polite">
{toasts.map(toast => (
<div key={toast.id} className={`toast toast-${toast.type}`}>
<span>{toast.message}</span>
<button onClick={() => onDismiss(toast.id)} aria-label="Dismiss">×</button>
</div>
))}
</div>
);
}
3. Update App.tsx
import { ToastProvider } from './context/ToastContext';
function App() {
return (
<WalletProvider>
<ToastProvider>
<ErrorBoundary>
<Router>{/* routes */}</Router>
</ErrorBoundary>
</ToastProvider>
</WalletProvider>
);
}
4. Replace Inline Error in TaskSubmissionForm.tsx
// BEFORE
{error && <div className="error-banner">{error}</div>}
// AFTER
const { showToast } = useToast();
// In submit handler:
if (error) showToast(error, 'error');
📁 Reference Files
| File Path |
Purpose |
frontend/src/App.tsx |
Wrap with ToastProvider |
frontend/src/components/agents/TaskSubmissionForm.tsx |
Replace inline error |
frontend/src/pages/dashboard.tsx |
Add toasts for fetch errors |
frontend/src/components/dashboard/RecentTasksTable.tsx |
Replace silent error |
frontend/src/services/api.ts |
Add toast on 503/401 |
frontend/src/components/common/ErrorBoundary.tsx |
Reference for error UI |
✅ Acceptance Criteria
🎯 Objective
Create a centralized toast/notification system for the frontend to replace the inconsistent inline error/success message patterns scattered across components.
📁 Files to Create
frontend/src/context/ToastContext.tsxfrontend/src/hooks/useToast.tsfrontend/src/components/common/Toast.tsxfrontend/src/components/common/Toast.css📁 Files to Modify
frontend/src/App.tsxToastProviderfrontend/src/components/agents/TaskSubmissionForm.tsxuseToastfrontend/src/pages/dashboard.tsxfrontend/src/components/dashboard/RecentTasksTable.tsxconsole.errorwith toastfrontend/src/services/api.ts🔍 Current Inconsistent Patterns
TaskSubmissionForm.tsxDashboardPage.tsxconsole.error()onlyRecentTasksTable.tsxsetTasks([])+console.erroruseTaskMonitor.tsapi.ts✅ Expected Implementation
1. Create
ToastContext.tsx2. Create
Toast.tsx3. Update
App.tsx4. Replace Inline Error in TaskSubmissionForm.tsx
📁 Reference Files
frontend/src/App.tsxfrontend/src/components/agents/TaskSubmissionForm.tsxfrontend/src/pages/dashboard.tsxfrontend/src/components/dashboard/RecentTasksTable.tsxfrontend/src/services/api.tsfrontend/src/components/common/ErrorBoundary.tsx✅ Acceptance Criteria
ToastContext.tsxwithshowToast(message, type, duration)Toast.tsxcomponent with auto-dismiss and close buttonToast.csswith slide-in animation and type-based colorsToastProviderinApp.tsxTaskSubmissionForm.tsxRecentTasksTable.tsxfor failed fetchesapi.tsfor 503 retry and 401 disconnectrole="alert"andaria-live="polite"