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>
This commit is contained in:
426
NOTIFICATION_SYSTEM.md
Normal file
426
NOTIFICATION_SYSTEM.md
Normal file
@@ -0,0 +1,426 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user