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>
427 lines
11 KiB
Markdown
427 lines
11 KiB
Markdown
# 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`:
|
|
|
|
```javascript
|
|
<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)
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
const myPromise = fetch("/api/data");
|
|
|
|
toast.promise(myPromise, {
|
|
pending: "Loading...",
|
|
success: "Success!",
|
|
error: "Failed!",
|
|
});
|
|
```
|
|
|
|
### Method 4: Loading toasts
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
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`:
|
|
|
|
```javascript
|
|
<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
|
|
|
|
```javascript
|
|
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`:
|
|
|
|
```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:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
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
|
|
|
|
- [react-toastify Documentation](https://fkhadra.github.io/react-toastify/)
|
|
- [Socket.IO Documentation](https://socket.io/docs/)
|
|
- [Bootstrap Colors](https://getbootstrap.com/docs/5.0/utilities/colors/)
|
|
|
|
## 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
|