Files
adopt-a-street/NOTIFICATION_SYSTEM.md
William Valentin a2d30385b5 feat: implement real-time notification toast system
Implemented comprehensive notification toast system integrating Socket.IO
with react-toastify for real-time user notifications.

Features:
- NotificationProvider component for automatic Socket.IO event handling
- Custom Bootstrap-themed toast styles with mobile responsiveness
- Four toast types: success, error, info, warning
- Auto-dismiss after 5 seconds with manual dismiss option
- Duplicate prevention using toast IDs
- Mobile-optimized full-width toasts
- Dark mode support
- 16 passing tests with full coverage

Toast notifications for:
- Connection status (connect/disconnect/reconnect)
- Event updates (new, updated, deleted, participants)
- Task updates (new, completed, updated, deleted)
- Street adoptions/unadoptions
- Achievement unlocks and badge awards
- Social updates (new posts, comments)
- Generic notifications with type-based styling

Usage:
import { notify } from '../context/NotificationProvider';
notify.success('Operation completed!');
notify.error('Something went wrong!');

Configuration:
- Position: top-right (configurable)
- Auto-close: 5 seconds (configurable)
- Max toasts: 5 concurrent
- Mobile responsive: full-width on ≤480px screens

Documentation:
- NOTIFICATION_SYSTEM.md: Complete usage guide
- NOTIFICATION_IMPLEMENTATION.md: Implementation summary
- frontend/src/examples/notificationExamples.js: Code examples

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
2025-11-03 13:20:15 -08:00

11 KiB

Notification Toast System Documentation

Overview

The Adopt-a-Street frontend now includes a comprehensive notification toast system that integrates with Socket.IO for real-time updates. The system uses react-toastify with custom styling that matches the Bootstrap theme.

Features

  • Real-time notifications via Socket.IO integration
  • Four toast types: success, error, info, warning
  • Auto-dismiss after 5 seconds (configurable)
  • Dismissible by clicking or using the close button
  • Mobile responsive with optimized positioning
  • Duplicate prevention using toast IDs
  • Custom styling matching Bootstrap theme
  • Accessibility compliant with ARIA attributes

Components

NotificationProvider

Located at: frontend/src/context/NotificationProvider.js

The NotificationProvider component automatically listens to Socket.IO events and displays appropriate toast notifications. It must be wrapped within both AuthProvider and SocketProvider.

Custom Styles

Located at: frontend/src/styles/toastStyles.css

Custom CSS that overrides react-toastify defaults to match the Bootstrap theme, including:

  • Bootstrap color scheme (success green, error red, warning yellow, info blue)
  • Responsive mobile layout
  • Custom animations
  • Dark mode support

Setup

The notification system is already integrated into App.js:

<AuthProvider>
  <SocketProvider>
    <NotificationProvider>
      <Router>
        {/* Your routes */}
      </Router>
      <ToastContainer
        position="top-right"
        autoClose={5000}
        hideProgressBar={false}
        newestOnTop={true}
        closeOnClick
        pauseOnHover
        draggable
        theme="light"
        limit={5}
      />
    </NotificationProvider>
  </SocketProvider>
</AuthProvider>

Usage

import { notify } from "../context/NotificationProvider";

// Success notification
notify.success("Operation completed successfully!");

// Error notification
notify.error("Something went wrong!");

// Info notification
notify.info("Here's some information");

// Warning notification
notify.warning("Please be careful!");

// With custom options
notify.success("Data saved!", {
  autoClose: 3000,
  position: "bottom-right",
});

// Dismiss specific toast
notify.dismiss("toast-id");

// Dismiss all toasts
notify.dismissAll();

Method 2: Using toast directly

import { toast } from "react-toastify";

// Basic usage
toast.success("Success message");
toast.error("Error message");
toast.info("Info message");
toast.warning("Warning message");

// Prevent duplicates with toast ID
toast.success("Message", {
  toastId: "unique-id",
});

// Custom content
toast.success(
  <div>
    <strong>Title</strong>
    <div>Message</div>
    <small>Details</small>
  </div>
);

Method 3: Promise-based toasts

const myPromise = fetch("/api/data");

toast.promise(myPromise, {
  pending: "Loading...",
  success: "Success!",
  error: "Failed!",
});

Method 4: Loading toasts

const toastId = toast.loading("Processing...");

// Later, update the toast
toast.update(toastId, {
  render: "Completed!",
  type: "success",
  isLoading: false,
  autoClose: 5000,
});

Socket.IO Events

The NotificationProvider automatically handles these Socket.IO events:

Connection Events

  • connect - Shows success toast when connected
  • disconnect - Shows error/warning toast when disconnected
  • reconnect - Shows success toast when reconnected
  • reconnect_error - Shows error toast if reconnection fails

Event Updates (eventUpdate)

  • new_event - New event created
  • participants_updated - Event participants changed
  • event_updated - Event details updated
  • event_deleted - Event cancelled

Task Updates (taskUpdate)

  • new_task - New task available
  • task_completed - Task completed by another user
  • task_updated - Task details updated
  • task_deleted - Task removed

Street Updates (streetUpdate)

  • street_adopted - Street adopted by a user
  • street_unadopted - Street available for adoption

Achievements

  • achievementUnlocked - User unlocked an achievement
  • badgeUnlocked - User earned a badge

Social Updates

  • newPost - New post created
  • newComment - New comment on a post

Generic Notification

  • notification - Fallback for any custom notification with type and message

Triggering Toasts from Components

Example: Form Submission

import React, { useState } from "react";
import { notify } from "../context/NotificationProvider";
import axios from "axios";

const MyComponent = () => {
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (data) => {
    try {
      setLoading(true);
      await axios.post("/api/endpoint", data);
      notify.success("Data submitted successfully!");
    } catch (error) {
      notify.error(error.response?.data?.msg || "Failed to submit");
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
    </form>
  );
};

Example: Real-time Updates

The NotificationProvider automatically handles Socket.IO events, but you can also trigger custom notifications:

import { useContext } from "react";
import { SocketContext } from "../context/SocketContext";
import { notify } from "../context/NotificationProvider";

const MyComponent = () => {
  const { emit } = useContext(SocketContext);

  const sendNotification = () => {
    // This will be handled by the NotificationProvider
    emit("notification", {
      type: "success",
      message: "Custom notification message",
    });
  };

  return <button onClick={sendNotification}>Send Notification</button>;
};

Configuration Options

ToastContainer Options

Configured in App.js:

<ToastContainer
  position="top-right"        // Position: top-left, top-right, top-center, bottom-left, bottom-right, bottom-center
  autoClose={5000}            // Auto close after 5 seconds
  hideProgressBar={false}     // Show progress bar
  newestOnTop={true}          // Stack newest toasts on top
  closeOnClick               // Close on click
  rtl={false}                // Right-to-left support
  pauseOnFocusLoss           // Pause when window loses focus
  draggable                  // Allow dragging to dismiss
  pauseOnHover               // Pause timer on hover
  theme="light"              // Theme: light, dark, colored
  limit={5}                  // Maximum number of toasts
/>

Individual Toast Options

toast.success("Message", {
  position: "top-right",
  autoClose: 5000,
  hideProgressBar: false,
  closeOnClick: true,
  pauseOnHover: true,
  draggable: true,
  toastId: "unique-id",      // Prevent duplicates
  onOpen: () => {},          // Callback on open
  onClose: () => {},         // Callback on close
  transition: Bounce,        // Custom transition
  className: "custom-class", // Custom CSS class
  bodyClassName: "body",     // Custom body class
  progressClassName: "bar",  // Custom progress bar class
  closeButton: <button />,   // Custom close button
});

Mobile Responsiveness

The toast system is fully responsive:

  • Desktop: Positioned in top-right corner (configurable)
  • Mobile (≤480px): Full-width toasts at the top of the screen
  • Touch-friendly: Large touch targets for dismissing
  • Smooth animations: Optimized for mobile performance

Accessibility

  • ARIA attributes for screen readers
  • Keyboard navigation support
  • Focus management
  • Semantic HTML structure
  • Color contrast compliance

Best Practices

  1. Prevent duplicate toasts: Always use toastId for repeated actions
  2. Keep messages concise: Aim for 1-2 sentences
  3. Use appropriate types:
    • success for completed actions
    • error for failures
    • warning for cautions
    • info for neutral information
  4. Don't spam: Limit notifications to important events
  5. Provide context: Include relevant details (e.g., "Task 'Clean Street' completed")
  6. Auto-dismiss: Use appropriate auto-close times (3-7 seconds)
  7. Persistent toasts: Set autoClose: false for critical errors

Styling Customization

To customize toast styles, edit frontend/src/styles/toastStyles.css:

/* Change colors */
:root {
  --toastify-color-success: #28a745;
  --toastify-color-error: #dc3545;
  --toastify-color-warning: #ffc107;
  --toastify-color-info: #17a2b8;
}

/* Custom toast style */
.Toastify__toast--success {
  border-left: 4px solid var(--toastify-color-success);
}

/* Custom animations */
@keyframes customAnimation {
  /* your animation */
}

Backend Integration

To trigger notifications from the backend, emit Socket.IO events:

// In backend route
const io = req.app.get("io");

// Notify all connected users
io.emit("notification", {
  type: "info",
  message: "System maintenance in 5 minutes",
});

// Notify specific user
io.to(userSocketId).emit("achievementUnlocked", {
  badge: {
    name: "First Adoption",
    description: "Adopted your first street!",
  },
});

// Notify event participants
io.to(`event_${eventId}`).emit("eventUpdate", {
  type: "participants_updated",
  eventId,
  participants: updatedParticipants,
});

Troubleshooting

Toasts not appearing

  1. Check that ToastContainer is in App.js
  2. Verify NotificationProvider is wrapping your app
  3. Check browser console for errors
  4. Ensure Socket.IO is connected

Duplicate toasts

  1. Add toastId to prevent duplicates
  2. Check if multiple components are triggering the same event
  3. Use toast.isActive(toastId) to check before showing

Styling issues

  1. Verify toastStyles.css is imported in App.js
  2. Check for CSS conflicts with Bootstrap
  3. Inspect element to verify classes are applied
  4. Clear browser cache

Testing

Example test for components using notifications:

import { render, screen, waitFor } from "@testing-library/react";
import { toast } from "react-toastify";
import MyComponent from "./MyComponent";

jest.mock("react-toastify", () => ({
  toast: {
    success: jest.fn(),
    error: jest.fn(),
  },
}));

test("shows success toast on submit", async () => {
  render(<MyComponent />);
  
  // Trigger action
  fireEvent.click(screen.getByText("Submit"));
  
  await waitFor(() => {
    expect(toast.success).toHaveBeenCalledWith("Success message");
  });
});

Resources

Support

For issues or questions:

  1. Check this documentation
  2. Review notificationExamples.js for usage examples
  3. Check browser console for errors
  4. Review Socket.IO connection status