Notifications
{notifications.length > 0 && (
diff --git a/client/src/components/common/ToastContainer.jsx b/client/src/components/common/ToastContainer.jsx
new file mode 100644
index 0000000..27a0d8f
--- /dev/null
+++ b/client/src/components/common/ToastContainer.jsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import { useNotifications } from '../../context/NotificationContext';
+import { X, CheckCircle, AlertCircle, AlertTriangle, Info } from 'lucide-react';
+
+const ToastIcon = ({ type }) => {
+ switch (type) {
+ case 'success':
+ return
;
+ case 'error':
+ return
;
+ case 'warning':
+ return
;
+ default:
+ return
;
+ }
+};
+
+const ToastNotification = ({ notification, onRemove }) => {
+ const typeStyles = {
+ success: 'border-green-200 bg-green-50 text-green-800',
+ error: 'border-red-200 bg-red-50 text-red-800',
+ warning: 'border-yellow-200 bg-yellow-50 text-yellow-800',
+ info: 'border-blue-200 bg-blue-50 text-blue-800'
+ };
+
+ return (
+
+
+
+
+
+
+
+ {notification.title && (
+
+ {notification.title}
+
+ )}
+
+ {notification.message}
+
+
+
+
+
+
+
+
+ );
+};
+
+const ToastContainer = () => {
+ const { notifications, removeNotification } = useNotifications();
+
+ // Filter notifications that should appear as toasts (temporary ones)
+ const toastNotifications = notifications.filter(n => n.isToast);
+
+ if (toastNotifications.length === 0) return null;
+
+ return (
+
+
+ {toastNotifications.map((notification) => (
+
+ ))}
+
+
+ );
+};
+
+export default ToastContainer;
\ No newline at end of file
diff --git a/client/src/components/dashboard/QuickActions.jsx b/client/src/components/dashboard/QuickActions.jsx
index a29c4c7..0ed70ba 100644
--- a/client/src/components/dashboard/QuickActions.jsx
+++ b/client/src/components/dashboard/QuickActions.jsx
@@ -1,5 +1,6 @@
import React from 'react';
import { Button } from '../common';
+import { useToast } from '../../hooks/useToast';
import {
Plus,
Calendar,
@@ -10,6 +11,7 @@ import {
} from 'lucide-react';
const QuickActions = ({ onAction, className = '' }) => {
+ const { toast } = useToast();
const actions = [
{
id: 'create-task',
@@ -75,19 +77,19 @@ const QuickActions = ({ onAction, className = '' }) => {
switch (actionId) {
case 'create-task':
// Navigate to task creation or open modal
- console.log('Create task clicked');
+ toast.info('Task creation feature coming soon');
break;
case 'create-project':
// Navigate to project creation
- console.log('Create project clicked');
+ toast.info('Project creation feature coming soon');
break;
- case 'schedule-meeting':
+ case 'schedule-meeting':
// Open calendar scheduling
- console.log('Schedule meeting clicked');
+ toast.info('Meeting scheduling feature coming soon');
break;
case 'invite-member':
// Open invite modal
- console.log('Invite member clicked');
+ toast.info('Member invitation feature coming soon');
break;
case 'view-calendar':
// Navigate to calendar
@@ -98,7 +100,7 @@ const QuickActions = ({ onAction, className = '' }) => {
window.location.href = '/tasks';
break;
default:
- console.log('Unknown action:', actionId);
+ toast.warning('Unknown action requested');
}
}
};
diff --git a/client/src/components/tasks/TaskModal.jsx b/client/src/components/tasks/TaskModal.jsx
index da94881..bd7bf2b 100644
--- a/client/src/components/tasks/TaskModal.jsx
+++ b/client/src/components/tasks/TaskModal.jsx
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { taskService } from '../../services/taskService';
import { userService } from '../../services/userService';
import { projectService } from '../../services/projectService';
+import { useToast } from '../../hooks/useToast';
import { X, Calendar, User, Flag, FileText, FolderOpen } from 'lucide-react';
const TaskModal = ({ isOpen, onClose, task, projectId, onSave }) => {
@@ -18,6 +19,7 @@ const TaskModal = ({ isOpen, onClose, task, projectId, onSave }) => {
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState({});
+ const { toast } = useToast();
useEffect(() => {
if (isOpen) {
@@ -56,6 +58,7 @@ const TaskModal = ({ isOpen, onClose, task, projectId, onSave }) => {
setUsers(Array.isArray(usersData) ? usersData : []);
} catch (error) {
console.error('Error fetching users:', error);
+ toast.error('Failed to load users');
setUsers([]);
}
};
@@ -67,7 +70,7 @@ const TaskModal = ({ isOpen, onClose, task, projectId, onSave }) => {
const projectsData = response.data?.projects || response.projects || response.data || [];
setProjects(Array.isArray(projectsData) ? projectsData : []);
} catch (error) {
- console.error('Error fetching projects:', error);
+ toast.error('Failed to load projects');
setProjects([]);
}
};
@@ -129,18 +132,19 @@ const TaskModal = ({ isOpen, onClose, task, projectId, onSave }) => {
let response;
if (task) {
response = await taskService.updateTask(task._id, submitData);
+ toast.success('Task updated successfully');
} else {
response = await taskService.createTask(submitData);
+ toast.success('Task created successfully');
}
onSave(response.data);
onClose();
} catch (error) {
- console.error('Error saving task:', error);
- console.error('Error response:', error.response?.data);
- console.error('Submitted data:', submitData);
+ const errorMessage = error.response?.data?.message || 'Failed to save task';
+ toast.error(errorMessage);
setErrors({
- submit: error.response?.data?.message || 'Failed to save task'
+ submit: errorMessage
});
} finally {
setLoading(false);
diff --git a/client/src/hooks/useToast.js b/client/src/hooks/useToast.js
new file mode 100644
index 0000000..415935a
--- /dev/null
+++ b/client/src/hooks/useToast.js
@@ -0,0 +1,57 @@
+import { useNotifications } from '../context/NotificationContext';
+
+export const useToast = () => {
+ const { addNotification } = useNotifications();
+
+ const toast = {
+ success: (message, title = '') => {
+ addNotification({
+ id: Date.now() + Math.random(),
+ type: 'success',
+ title,
+ message,
+ timestamp: new Date(),
+ isToast: true,
+ autoClose: true
+ });
+ },
+
+ error: (message, title = '') => {
+ addNotification({
+ id: Date.now() + Math.random(),
+ type: 'error',
+ title,
+ message,
+ timestamp: new Date(),
+ isToast: true,
+ autoClose: true
+ });
+ },
+
+ warning: (message, title = '') => {
+ addNotification({
+ id: Date.now() + Math.random(),
+ type: 'warning',
+ title,
+ message,
+ timestamp: new Date(),
+ isToast: true,
+ autoClose: true
+ });
+ },
+
+ info: (message, title = '') => {
+ addNotification({
+ id: Date.now() + Math.random(),
+ type: 'info',
+ title,
+ message,
+ timestamp: new Date(),
+ isToast: true,
+ autoClose: true
+ });
+ }
+ };
+
+ return { toast };
+};
\ No newline at end of file
diff --git a/client/src/pages/ProjectBoard.jsx b/client/src/pages/ProjectBoard.jsx
index c1081fd..4df26f3 100644
--- a/client/src/pages/ProjectBoard.jsx
+++ b/client/src/pages/ProjectBoard.jsx
@@ -4,6 +4,7 @@ import { projectService } from '../services/projectService';
import Layout from '../components/layout/Layout';
import { KanbanBoard } from '../components/board';
import { LoadingSpinner, Button } from '../components/common';
+import { useToast } from '../hooks/useToast';
import {
ArrowLeft,
Settings,
@@ -20,6 +21,7 @@ const ProjectBoard = () => {
const { projectId } = useParams();
const [project, setProject] = useState(null);
const [loading, setLoading] = useState(true);
+ const { toast } = useToast();
const [showFilters, setShowFilters] = useState(false);
useEffect(() => {
@@ -32,14 +34,15 @@ const ProjectBoard = () => {
const projectResponse = await projectService.getProject(projectId);
setProject(projectResponse.data);
} catch (error) {
- console.error('Error fetching project:', error);
+ const errorMessage = error.response?.data?.message || 'Failed to fetch project details';
+ toast.error(errorMessage);
} finally {
setLoading(false);
}
};
const handleTaskUpdate = (task) => {
- console.log('Task updated:', task);
+ toast.success('Task updated successfully');
};
const getStatusColor = (status) => {
diff --git a/client/src/services/notificationService.js b/client/src/services/notificationService.js
index 19f9fc0..cc94e4c 100644
--- a/client/src/services/notificationService.js
+++ b/client/src/services/notificationService.js
@@ -109,12 +109,10 @@ class NotificationService {
subscribeToNotifications(callback) {
// This would be used with Socket.io context
// The actual implementation is in the NotificationPanel component
- console.log('Subscribe to notifications:', callback);
}
unsubscribeFromNotifications(callback) {
// This would be used with Socket.io context
- console.log('Unsubscribe from notifications:', callback);
}
}