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>
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
Method 1: Using the notify utility (Recommended)
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 connecteddisconnect- Shows error/warning toast when disconnectedreconnect- Shows success toast when reconnectedreconnect_error- Shows error toast if reconnection fails
Event Updates (eventUpdate)
new_event- New event createdparticipants_updated- Event participants changedevent_updated- Event details updatedevent_deleted- Event cancelled
Task Updates (taskUpdate)
new_task- New task availabletask_completed- Task completed by another usertask_updated- Task details updatedtask_deleted- Task removed
Street Updates (streetUpdate)
street_adopted- Street adopted by a userstreet_unadopted- Street available for adoption
Achievements
achievementUnlocked- User unlocked an achievementbadgeUnlocked- User earned a badge
Social Updates
newPost- New post creatednewComment- New comment on a post
Generic Notification
notification- Fallback for any custom notification withtypeandmessage
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
- Prevent duplicate toasts: Always use
toastIdfor repeated actions - Keep messages concise: Aim for 1-2 sentences
- Use appropriate types:
successfor completed actionserrorfor failureswarningfor cautionsinfofor neutral information
- Don't spam: Limit notifications to important events
- Provide context: Include relevant details (e.g., "Task 'Clean Street' completed")
- Auto-dismiss: Use appropriate auto-close times (3-7 seconds)
- Persistent toasts: Set
autoClose: falsefor 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
- Check that
ToastContaineris inApp.js - Verify
NotificationProvideris wrapping your app - Check browser console for errors
- Ensure Socket.IO is connected
Duplicate toasts
- Add
toastIdto prevent duplicates - Check if multiple components are triggering the same event
- Use
toast.isActive(toastId)to check before showing
Styling issues
- Verify
toastStyles.cssis imported inApp.js - Check for CSS conflicts with Bootstrap
- Inspect element to verify classes are applied
- 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:
- Check this documentation
- Review
notificationExamples.jsfor usage examples - Check browser console for errors
- Review Socket.IO connection status