Frontend Feedback System
Centralized notification architecture with Notification Manager, Error Code Registry, Axios interceptors, and reusable UI feedback components.
Frontend Feedback System
The SLMS frontend implements a centralized, enterprise-grade feedback system that ensures users always know what happened, why it happened, and what to do next.
Architecture
User Action
│
▼
Axios Client (services/api/client.ts)
│
▼
Response Interceptors
│
▼
Error Code Registry (constants/errorCodes.ts)
│
▼
Notification Manager (services/notification.service.ts)
│
├── Toast (success / info / warning / error)
├── Banner (network / system / critical)
├── Modal (confirmation dialogs)
├── Inline Validation (form field errors)
└── Full-page State (loading / empty / error / offline)Error Code Registry
File: src/constants/errorCodes.ts
Maps backend error codes to rich, data-driven UI objects. Each entry defines not just a message, but the full rendering strategy:
export const ERROR_CODES: Record<string, ErrorDetails> = {
AUTH_REQUIRED: {
title: 'Authentication Required',
message: 'Please sign in to access this resource.',
severity: 'warning',
action: 'Login',
icon: 'Lock',
},
RATE_LIMITED: {
title: 'Too Many Requests',
message: 'You are making too many requests. Please try again later.',
severity: 'warning',
icon: 'Hourglass',
},
// ...
};The frontend never parses message text. It switches on the code field for stable, localizable behavior.
Notification Manager
File: src/services/notification.service.ts
Singleton service that wraps sonner toasts and the useBannerStore:
notification.success('Book added successfully');
notification.error('Unable to save changes');
notification.warning('Session expiring soon');
notification.info('New version available');
notification.banner('System maintenance in progress', 'warning');
notification.clearBanner();
const confirmed = await notification.confirm('Delete Book?', 'This action cannot be undone.');Anti-Spam Features
- Duplicate Detection: Creates a hash-based toast ID. If a toast with the same content is already visible, the duplicate is silently ignored.
- Toast Queue: Sonner manages a maximum of visible toasts. Excess toasts queue automatically.
Axios Interceptors
File: src/services/api/client.ts
The global Axios instance handles all API communication. Pages never call Axios directly or interpret HTTP status codes.
Request Flow
API Call → Axios Client
│
├── Network Error (no response) → Banner: "Unable to connect"
│
├── 401 Unauthorized
│ ├── If refreshing → queue request
│ ├── Attempt /auth/refresh
│ │ ├── Success → retry original request
│ │ └── Failure → Toast: "Session expired" → redirect /login
│
├── 400 VALIDATION_FAILED → pass through (handled by React Hook Form inline)
│
└── Other errors → Lookup Error Code Registry → Notification ManagerAutomatic Token Refresh
When a request returns 401, the interceptor:
- Queues all concurrent failing requests
- Calls
POST /auth/refreshwith credentials - On success, replays all queued requests with the new token
- On failure, redirects to
/login
State Stores
useBannerStore (Zustand)
File: src/stores/useBannerStore.ts
Manages the global alert banner for critical system notifications:
const { activeBanner, showBanner, hideBanner } = useBannerStore();Severities: info, warning, error, critical
useLoadingStore (Zustand)
File: src/stores/useLoadingStore.ts
Manages global and scoped loading states:
const { startRequest, endRequest, isLoading } = useLoadingStore();
startRequest('save-book'); // Mark loading
endRequest('save-book'); // Clear loading
isLoading('save-book'); // Check stateUI Components
Confirm Dialog (Promise-Based)
File: src/components/feedback/ConfirmDialog.tsx
Built on Radix UI Alert Dialog. Renders a global confirmation modal controlled via custom events:
import { confirm } from '@/components/feedback/ConfirmDialog';
const confirmed = await confirm({
title: 'Delete Student?',
message: 'This action cannot be undone.',
confirmText: 'Delete',
cancelText: 'Cancel',
});
if (confirmed) { /* proceed */ }Error Boundary
File: src/components/feedback/ErrorBoundary.tsx
React class component that catches unhandled errors and renders a graceful fallback:
┌───────────────────────────────────┐
│ Oops! │
│ Something went wrong. │
│ │
│ [Reload Page] │
└───────────────────────────────────┘Empty State
File: src/components/ui/EmptyState.tsx
Reusable component for when data collections are empty:
<EmptyState
icon={<BookX size={48} />}
title="No books found"
description="Add your first book to get started"
action={<Button>Add Book</Button>}
/>Skeleton Loaders
File: src/components/ui/Skeleton.tsx
Pre-configured skeleton layouts for consistent loading states:
<Skeleton />— base animated placeholder<CardSkeleton />— card with image + text lines<TableSkeleton rows={5} />— table header + rows<FormSkeleton />— label + input pairs
Form Field
File: src/components/ui/FormField.tsx
Unified form field wrapper for React Hook Form + Zod:
<FormField
name="email"
label="Email Address"
type="email"
placeholder="you@college.edu"
description="Enter your college email"
required
/>Automatically renders:
- Label with required indicator
- Input with error-state styling
- Inline error message with ✗ icon
- Helper description text
Offline Detection
File: src/hooks/useOfflineDetection.ts
Listens to window.online and window.offline events. Automatically shows/hides a non-dismissible warning banner:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠ You are currently offline.
Some actions are unavailable.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Notification Priority Matrix
| Priority | Component | Example |
|---|---|---|
| Critical | Full Screen / Error Boundary | React crash, system outage |
| High | Banner | Network error, maintenance |
| Medium | Toast | Book added, session refreshed |
| Low | Status Label | "Saved just now" |
| Field | Inline | Validation error on specific input |