Smart Library Docs

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 Manager

Automatic Token Refresh

When a request returns 401, the interceptor:

  1. Queues all concurrent failing requests
  2. Calls POST /auth/refresh with credentials
  3. On success, replays all queued requests with the new token
  4. 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 state

UI 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

PriorityComponentExample
CriticalFull Screen / Error BoundaryReact crash, system outage
HighBannerNetwork error, maintenance
MediumToastBook added, session refreshed
LowStatus Label"Saved just now"
FieldInlineValidation error on specific input

On this page