- Update consistent Delete Confirmation Modal

This commit is contained in:
sean-eskerium
2025-08-21 01:25:15 -04:00
parent 97a280461a
commit a194ec9a74
9 changed files with 275 additions and 129 deletions

View File

@@ -5,7 +5,7 @@ import { ToolTestingPanel } from './ToolTestingPanel';
import { Button } from '../ui/Button'; import { Button } from '../ui/Button';
import { mcpClientService, MCPClient, MCPClientConfig } from '../../services/mcpClientService'; import { mcpClientService, MCPClient, MCPClientConfig } from '../../services/mcpClientService';
import { useToast } from '../../contexts/ToastContext'; import { useToast } from '../../contexts/ToastContext';
import { DeleteConfirmModal } from '../../pages/ProjectPage'; import { DeleteConfirmModal } from '../ui/DeleteConfirmModal';
// Client interface (keeping for backward compatibility) // Client interface (keeping for backward compatibility)
export interface Client { export interface Client {
@@ -710,17 +710,30 @@ const EditClientDrawer: React.FC<EditClientDrawerProps> = ({ client, isOpen, onC
} }
}; };
const handleDelete = async () => { const handleDelete = () => {
if (confirm(`Are you sure you want to delete "${client.name}"?`)) { setClientToDelete(client);
setShowDeleteConfirm(true);
};
const confirmDeleteClient = async () => {
if (!clientToDelete) return;
try { try {
await mcpClientService.deleteClient(client.id); await mcpClientService.deleteClient(clientToDelete.id);
onClose(); onClose();
// Trigger a reload of the clients list // Trigger a reload of the clients list
window.location.reload(); window.location.reload();
} catch (error) { } catch (error) {
setError(error instanceof Error ? error.message : 'Failed to delete client'); setError(error instanceof Error ? error.message : 'Failed to delete client');
} finally {
setShowDeleteConfirm(false);
setClientToDelete(null);
} }
} };
const cancelDeleteClient = () => {
setShowDeleteConfirm(false);
setClientToDelete(null);
}; };
if (!isOpen) return null; if (!isOpen) return null;
@@ -853,6 +866,16 @@ const EditClientDrawer: React.FC<EditClientDrawerProps> = ({ client, isOpen, onC
</div> </div>
</form> </form>
</div> </div>
{/* Delete Confirmation Modal */}
{showDeleteConfirm && clientToDelete && (
<DeleteConfirmModal
itemName={clientToDelete.name}
onConfirm={confirmDeleteClient}
onCancel={cancelDeleteClient}
type="client"
/>
)}
</div> </div>
); );
}; };

View File

@@ -14,6 +14,7 @@ import { MilkdownEditor } from './MilkdownEditor';
import { VersionHistoryModal } from './VersionHistoryModal'; import { VersionHistoryModal } from './VersionHistoryModal';
import { PRPViewer } from '../prp'; import { PRPViewer } from '../prp';
import { DocumentCard, NewDocumentCard } from './DocumentCard'; import { DocumentCard, NewDocumentCard } from './DocumentCard';
import { DeleteConfirmModal } from '../ui/DeleteConfirmModal';
@@ -514,6 +515,10 @@ export const DocsTab = ({
// Document state // Document state
const [documents, setDocuments] = useState<ProjectDoc[]>([]); const [documents, setDocuments] = useState<ProjectDoc[]>([]);
const [selectedDocument, setSelectedDocument] = useState<ProjectDoc | null>(null); const [selectedDocument, setSelectedDocument] = useState<ProjectDoc | null>(null);
// Delete confirmation modal state
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [documentToDelete, setDocumentToDelete] = useState<{ id: string; title: string } | null>(null);
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -575,7 +580,14 @@ export const DocsTab = ({
document_type: doc.document_type || 'document' document_type: doc.document_type || 'document'
})); }));
setDocuments(projectDocuments); // Merge with existing documents, preserving any temporary documents
setDocuments(prev => {
// Keep any temporary documents (ones with temp- prefix)
const tempDocs = prev.filter(doc => doc.id.startsWith('temp-'));
// Merge temporary docs with loaded docs
return [...projectDocuments, ...tempDocs];
});
// Auto-select first document if available and no document is currently selected // Auto-select first document if available and no document is currently selected
if (projectDocuments.length > 0 && !selectedDocument) { if (projectDocuments.length > 0 && !selectedDocument) {
@@ -598,6 +610,26 @@ export const DocsTab = ({
const template = DOCUMENT_TEMPLATES[templateKey as keyof typeof DOCUMENT_TEMPLATES]; const template = DOCUMENT_TEMPLATES[templateKey as keyof typeof DOCUMENT_TEMPLATES];
if (!template) return; if (!template) return;
// Create a temporary document for optimistic update
const tempDocument: ProjectDoc = {
id: `temp-${Date.now()}`,
title: template.name,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
content: template.content,
document_type: template.document_type
};
// Optimistically add the document to the UI immediately
console.log('[DocsTab] Adding temporary document:', tempDocument);
setDocuments(prev => {
const updated = [...prev, tempDocument];
console.log('[DocsTab] Documents after optimistic add:', updated);
return updated;
});
setSelectedDocument(tempDocument);
setShowTemplateModal(false);
try { try {
setIsSaving(true); setIsSaving(true);
@@ -608,15 +640,22 @@ export const DocsTab = ({
document_type: template.document_type document_type: template.document_type
}); });
// Add to documents list // Replace temporary document with the real one
setDocuments(prev => [...prev, newDocument]); setDocuments(prev => prev.map(doc =>
doc.id === tempDocument.id ? newDocument : doc
));
setSelectedDocument(newDocument); setSelectedDocument(newDocument);
console.log('Document created successfully via API:', newDocument); console.log('Document created successfully via API:', newDocument);
showToast('Document created successfully', 'success'); showToast('Document created successfully', 'success');
setShowTemplateModal(false);
} catch (error) { } catch (error) {
console.error('Failed to create document:', error); console.error('Failed to create document:', error);
// Remove the temporary document on error
setDocuments(prev => prev.filter(doc => doc.id !== tempDocument.id));
setSelectedDocument(null);
setShowTemplateModal(true); // Re-open the modal
showToast( showToast(
error instanceof Error ? error.message : 'Failed to create document', error instanceof Error ? error.message : 'Failed to create document',
'error' 'error'
@@ -783,6 +822,34 @@ export const DocsTab = ({
} }
}; };
// Delete confirmation handlers
const confirmDeleteDocument = async () => {
if (!documentToDelete || !project?.id) return;
try {
// Call API to delete from database first
await projectService.deleteDocument(project.id, documentToDelete.id);
// Then remove from local state
setDocuments(prev => prev.filter(d => d.id !== documentToDelete.id));
if (selectedDocument?.id === documentToDelete.id) {
setSelectedDocument(documents.find(d => d.id !== documentToDelete.id) || null);
}
showToast('Document deleted', 'success');
} catch (error) {
console.error('Failed to delete document:', error);
showToast('Failed to delete document', 'error');
} finally {
setShowDeleteConfirm(false);
setDocumentToDelete(null);
}
};
const cancelDeleteDocument = () => {
setShowDeleteConfirm(false);
setDocumentToDelete(null);
};
const handleProgressComplete = (data: CrawlProgressData) => { const handleProgressComplete = (data: CrawlProgressData) => {
console.log('Crawl completed:', data); console.log('Crawl completed:', data);
setProgressItems(prev => prev.filter(item => item.progressId !== data.progressId)); setProgressItems(prev => prev.filter(item => item.progressId !== data.progressId));
@@ -935,22 +1002,11 @@ export const DocsTab = ({
document={doc} document={doc}
isActive={selectedDocument?.id === doc.id} isActive={selectedDocument?.id === doc.id}
onSelect={setSelectedDocument} onSelect={setSelectedDocument}
onDelete={async (docId) => { onDelete={(docId) => {
if (!project?.id) return; const doc = documents.find(d => d.id === docId);
if (doc) {
try { setDocumentToDelete({ id: docId, title: doc.title });
// Call API to delete from database first setShowDeleteConfirm(true);
await projectService.deleteDocument(project.id, docId);
// Then remove from local state
setDocuments(prev => prev.filter(d => d.id !== docId));
if (selectedDocument?.id === docId) {
setSelectedDocument(documents.find(d => d.id !== docId) || null);
}
showToast('Document deleted', 'success');
} catch (error) {
console.error('Failed to delete document:', error);
showToast('Failed to delete document', 'error');
} }
}} }}
isDarkMode={isDarkMode} isDarkMode={isDarkMode}
@@ -1099,6 +1155,16 @@ export const DocsTab = ({
}} }}
/> />
)} )}
{/* Delete Confirmation Modal */}
{showDeleteConfirm && documentToDelete && (
<DeleteConfirmModal
itemName={documentToDelete.title}
onConfirm={confirmDeleteDocument}
onCancel={cancelDeleteDocument}
type="document"
/>
)}
</div> </div>
); );
}; };

View File

@@ -117,9 +117,7 @@ export const DocumentCard: React.FC<DocumentCardProps> = ({
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (confirm(`Delete "${document.title}"?`)) {
onDelete(document.id); onDelete(document.id);
}
}} }}
className="absolute top-2 right-2 p-1 rounded-md bg-red-500/10 hover:bg-red-500/20 text-red-600 dark:text-red-400 transition-colors" className="absolute top-2 right-2 p-1 rounded-md bg-red-500/10 hover:bg-red-500/20 text-red-600 dark:text-red-400 transition-colors"
aria-label={`Delete ${document.title}`} aria-label={`Delete ${document.title}`}

View File

@@ -1,7 +1,7 @@
import React, { useRef, useState, useCallback } from 'react'; import React, { useRef, useState, useCallback } from 'react';
import { useDrag, useDrop } from 'react-dnd'; import { useDrag, useDrop } from 'react-dnd';
import { useToast } from '../../contexts/ToastContext'; import { useToast } from '../../contexts/ToastContext';
import { DeleteConfirmModal } from '../../pages/ProjectPage'; import { DeleteConfirmModal } from '../ui/DeleteConfirmModal';
import { CheckSquare, Square, Trash2, ArrowRight } from 'lucide-react'; import { CheckSquare, Square, Trash2, ArrowRight } from 'lucide-react';
import { projectService } from '../../services/projectService'; import { projectService } from '../../services/projectService';
import { Task } from './TaskTableView'; // Import Task interface import { Task } from './TaskTableView'; // Import Task interface

View File

@@ -2,7 +2,7 @@ import React, { useState, useCallback, useRef, useEffect } from 'react';
import { useDrag, useDrop } from 'react-dnd'; import { useDrag, useDrop } from 'react-dnd';
import { Check, Trash2, Edit, Tag, User, Bot, Clipboard, Save, Plus } from 'lucide-react'; import { Check, Trash2, Edit, Tag, User, Bot, Clipboard, Save, Plus } from 'lucide-react';
import { useToast } from '../../contexts/ToastContext'; import { useToast } from '../../contexts/ToastContext';
import { DeleteConfirmModal } from '../../pages/ProjectPage'; import { DeleteConfirmModal } from '../ui/DeleteConfirmModal';
import { projectService } from '../../services/projectService'; import { projectService } from '../../services/projectService';
import { ItemTypes, getAssigneeIcon, getAssigneeGlow, getOrderColor, getOrderGlow } from '../../lib/task-utils'; import { ItemTypes, getAssigneeIcon, getAssigneeGlow, getOrderColor, getOrderGlow } from '../../lib/task-utils';
import { DraggableTaskCard } from './DraggableTaskCard'; import { DraggableTaskCard } from './DraggableTaskCard';

View File

@@ -176,7 +176,9 @@ export const TasksTab = ({
// Check if this is an echo of a local update // Check if this is an echo of a local update
const localUpdateTime = localUpdates[updatedTask.id]; const localUpdateTime = localUpdates[updatedTask.id];
if (localUpdateTime && Date.now() - localUpdateTime < 2000) { console.log(`[Socket] Checking for echo - Task ${updatedTask.id}, localUpdateTime: ${localUpdateTime}, current time: ${Date.now()}, diff: ${localUpdateTime ? Date.now() - localUpdateTime : 'N/A'}`);
if (localUpdateTime && Date.now() - localUpdateTime < 5000) { // Increased window to 5 seconds
console.log('[Socket] Skipping echo update for locally updated task:', updatedTask.id); console.log('[Socket] Skipping echo update for locally updated task:', updatedTask.id);
// Clean up the local update marker after the echo protection window // Clean up the local update marker after the echo protection window
setTimeout(() => { setTimeout(() => {
@@ -185,9 +187,10 @@ export const TasksTab = ({
delete newUpdates[updatedTask.id]; delete newUpdates[updatedTask.id];
return newUpdates; return newUpdates;
}); });
}, 2000); }, 5000);
return; return;
} }
console.log('[Socket] Not an echo, applying update for task:', updatedTask.id);
// Skip updates while modal is open for the same task to prevent conflicts // Skip updates while modal is open for the same task to prevent conflicts
if (isModalOpen && editingTask?.id === updatedTask.id) { if (isModalOpen && editingTask?.id === updatedTask.id) {
@@ -553,18 +556,26 @@ export const TasksTab = ({
console.log(`[TasksTab] Moving task ${movingTask.title} from ${oldStatus} to ${newStatus} with order ${newOrder}`); console.log(`[TasksTab] Moving task ${movingTask.title} from ${oldStatus} to ${newStatus} with order ${newOrder}`);
// OPTIMISTIC UPDATE: Update UI immediately // OPTIMISTIC UPDATE: Update UI immediately
console.log(`[TasksTab] Applying optimistic move for task ${taskId} to ${newStatus}`);
setTasks(prev => { setTasks(prev => {
const updated = prev.map(task => task.id === taskId ? updatedTask : task); const updated = prev.map(task => task.id === taskId ? updatedTask : task);
console.log(`[TasksTab] Tasks after optimistic move:`, updated);
setTimeout(() => onTasksChange(updated), 0); setTimeout(() => onTasksChange(updated), 0);
return updated; return updated;
}); });
console.log(`[TasksTab] Optimistically updated UI for task ${taskId}`); console.log(`[TasksTab] Optimistically updated UI for task ${taskId}`);
// Mark this update as local to prevent echo when socket update arrives // Mark this update as local to prevent echo when socket update arrives
setLocalUpdates(prev => ({ const updateTime = Date.now();
console.log(`[TasksTab] Marking update as local for task ${taskId} at time ${updateTime}`);
setLocalUpdates(prev => {
const newUpdates = {
...prev, ...prev,
[taskId]: Date.now() [taskId]: updateTime
})); };
console.log('[TasksTab] LocalUpdates state:', newUpdates);
return newUpdates;
});
try { try {
// Then update the backend // Then update the backend
@@ -698,19 +709,30 @@ export const TasksTab = ({
const originalTask = tasks.find(t => t.id === taskId); const originalTask = tasks.find(t => t.id === taskId);
// Optimistically update the UI immediately // Optimistically update the UI immediately
setTasks(prevTasks => console.log(`[TasksTab] Applying optimistic update for task ${taskId}`, updates);
prevTasks.map(task => setTasks(prevTasks => {
const updated = prevTasks.map(task =>
task.id === taskId task.id === taskId
? { ...task, ...updates } ? { ...task, ...updates }
: task : task
)
); );
console.log(`[TasksTab] Tasks after optimistic update:`, updated);
// Notify parent of the optimistic update
setTimeout(() => onTasksChange(updated), 0);
return updated;
});
// Mark this update as local to prevent echo when socket update arrives // Mark this update as local to prevent echo when socket update arrives
setLocalUpdates(prev => ({ const updateTime = Date.now();
console.log(`[TasksTab] Marking update as local for task ${taskId} at time ${updateTime}`);
setLocalUpdates(prev => {
const newUpdates = {
...prev, ...prev,
[taskId]: Date.now() [taskId]: updateTime
})); };
console.log('[TasksTab] LocalUpdates state:', newUpdates);
return newUpdates;
});
try { try {
const updateData: Partial<UpdateTaskRequest> = {}; const updateData: Partial<UpdateTaskRequest> = {};

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { Trash2 } from 'lucide-react';
export interface DeleteConfirmModalProps {
itemName: string;
onConfirm: () => void;
onCancel: () => void;
type: 'project' | 'task' | 'client' | 'document' | 'knowledge-items' | 'feature' | 'data';
}
export const DeleteConfirmModal: React.FC<DeleteConfirmModalProps> = ({ itemName, onConfirm, onCancel, type }) => {
const getTitle = () => {
switch (type) {
case 'project': return 'Delete Project';
case 'task': return 'Delete Task';
case 'client': return 'Delete MCP Client';
case 'document': return 'Delete Document';
case 'knowledge-items': return 'Delete Knowledge Items';
case 'feature': return 'Delete Feature';
case 'data': return 'Delete Data';
}
};
const getMessage = () => {
switch (type) {
case 'project': return `Are you sure you want to delete the "${itemName}" project? This will also delete all associated tasks and documents and cannot be undone.`;
case 'task': return `Are you sure you want to delete the "${itemName}" task? This action cannot be undone.`;
case 'client': return `Are you sure you want to delete the "${itemName}" client? This will permanently remove its configuration and cannot be undone.`;
case 'document': return `Are you sure you want to delete the "${itemName}" document? This action cannot be undone.`;
case 'knowledge-items': return `Are you sure you want to delete ${itemName}? This will permanently remove the selected items from your knowledge base and cannot be undone.`;
case 'feature': return `Are you sure you want to delete the "${itemName}" feature? This action cannot be undone.`;
case 'data': return `Are you sure you want to delete this data? This action cannot be undone.`;
}
};
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
<div className="relative p-6 rounded-md backdrop-blur-md w-full max-w-md
bg-gradient-to-b from-white/80 to-white/60 dark:from-white/10 dark:to-black/30
border border-gray-200 dark:border-zinc-800/50
shadow-[0_10px_30px_-15px_rgba(0,0,0,0.1)] dark:shadow-[0_10px_30px_-15px_rgba(0,0,0,0.7)]
before:content-[''] before:absolute before:top-0 before:left-0 before:right-0 before:h-[2px]
before:rounded-t-[4px] before:bg-red-500
before:shadow-[0_0_10px_2px_rgba(239,68,68,0.4)] dark:before:shadow-[0_0_20px_5px_rgba(239,68,68,0.7)]">
<div className="relative z-10">
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<Trash2 className="w-6 h-6 text-red-600 dark:text-red-400" />
</div>
<div>
<h3 className="text-lg font-semibold text-gray-800 dark:text-white">
{getTitle()}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
This action cannot be undone
</p>
</div>
</div>
<p className="text-gray-700 dark:text-gray-300 mb-6">
{getMessage()}
</p>
<div className="flex justify-end gap-3">
<button
onClick={onCancel}
className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-colors"
>
Cancel
</button>
<button
onClick={onConfirm}
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md transition-colors shadow-lg shadow-red-600/20"
>
Delete
</button>
</div>
</div>
</div>
</div>
);
};

View File

@@ -18,6 +18,7 @@ import { KnowledgeTable } from '../components/knowledge-base/KnowledgeTable';
import { KnowledgeItemCard } from '../components/knowledge-base/KnowledgeItemCard'; import { KnowledgeItemCard } from '../components/knowledge-base/KnowledgeItemCard';
import { GroupedKnowledgeItemCard } from '../components/knowledge-base/GroupedKnowledgeItemCard'; import { GroupedKnowledgeItemCard } from '../components/knowledge-base/GroupedKnowledgeItemCard';
import { KnowledgeGridSkeleton, KnowledgeTableSkeleton } from '../components/knowledge-base/KnowledgeItemSkeleton'; import { KnowledgeGridSkeleton, KnowledgeTableSkeleton } from '../components/knowledge-base/KnowledgeItemSkeleton';
import { DeleteConfirmModal } from '../components/ui/DeleteConfirmModal';
import { GroupCreationModal } from '../components/knowledge-base/GroupCreationModal'; import { GroupCreationModal } from '../components/knowledge-base/GroupCreationModal';
const extractDomain = (url: string): string => { const extractDomain = (url: string): string => {
@@ -70,6 +71,10 @@ export const KnowledgeBasePage = () => {
const [isSelectionMode, setIsSelectionMode] = useState(false); const [isSelectionMode, setIsSelectionMode] = useState(false);
const [lastSelectedIndex, setLastSelectedIndex] = useState<number | null>(null); const [lastSelectedIndex, setLastSelectedIndex] = useState<number | null>(null);
// Delete confirmation modal state
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [itemsToDelete, setItemsToDelete] = useState<{ count: number; items: Set<string> } | null>(null);
const { showToast } = useToast(); const { showToast } = useToast();
// Single consolidated loading function - only loads data, no filtering // Single consolidated loading function - only loads data, no filtering
@@ -360,32 +365,43 @@ export const KnowledgeBasePage = () => {
if (selectedItems.size === 0) return; if (selectedItems.size === 0) return;
const count = selectedItems.size; const count = selectedItems.size;
const confirmed = window.confirm(`Are you sure you want to delete ${count} selected item${count > 1 ? 's' : ''}?`); setItemsToDelete({ count, items: new Set(selectedItems) });
setShowDeleteConfirm(true);
};
if (!confirmed) return; const confirmDeleteItems = async () => {
if (!itemsToDelete) return;
try { try {
// Delete each selected item // Delete each selected item
const deletePromises = Array.from(selectedItems).map(itemId => const deletePromises = Array.from(itemsToDelete.items).map(itemId =>
knowledgeBaseService.deleteKnowledgeItem(itemId) knowledgeBaseService.deleteKnowledgeItem(itemId)
); );
await Promise.all(deletePromises); await Promise.all(deletePromises);
// Remove deleted items from state // Remove deleted items from state
setKnowledgeItems(prev => prev.filter(item => !selectedItems.has(item.id))); setKnowledgeItems(prev => prev.filter(item => !itemsToDelete.items.has(item.id)));
// Clear selection // Clear selection
setSelectedItems(new Set()); setSelectedItems(new Set());
setIsSelectionMode(false); setIsSelectionMode(false);
showToast(`Successfully deleted ${count} item${count > 1 ? 's' : ''}`, 'success'); showToast(`Successfully deleted ${itemsToDelete.count} item${itemsToDelete.count > 1 ? 's' : ''}`, 'success');
} catch (error) { } catch (error) {
console.error('Failed to delete selected items:', error); console.error('Failed to delete selected items:', error);
showToast('Failed to delete some items', 'error'); showToast('Failed to delete some items', 'error');
} finally {
setShowDeleteConfirm(false);
setItemsToDelete(null);
} }
}; };
const cancelDeleteItems = () => {
setShowDeleteConfirm(false);
setItemsToDelete(null);
};
// Keyboard shortcuts // Keyboard shortcuts
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
@@ -1194,6 +1210,16 @@ export const KnowledgeBasePage = () => {
}} }}
/> />
)} )}
{/* Delete Confirmation Modal */}
{showDeleteConfirm && itemsToDelete && (
<DeleteConfirmModal
itemName={`${itemsToDelete.count} selected item${itemsToDelete.count > 1 ? 's' : ''}`}
onConfirm={confirmDeleteItems}
onCancel={cancelDeleteItems}
type="knowledge-items"
/>
)}
</div>; </div>;
}; };

View File

@@ -10,6 +10,7 @@ import { TasksTab } from '../components/project-tasks/TasksTab';
import { Button } from '../components/ui/Button'; import { Button } from '../components/ui/Button';
import { ChevronRight, ShoppingCart, Code, Briefcase, Layers, Plus, X, AlertCircle, Loader2, Heart, BarChart3, Trash2, Pin, ListTodo, Activity, CheckCircle2, Clipboard } from 'lucide-react'; import { ChevronRight, ShoppingCart, Code, Briefcase, Layers, Plus, X, AlertCircle, Loader2, Heart, BarChart3, Trash2, Pin, ListTodo, Activity, CheckCircle2, Clipboard } from 'lucide-react';
import { copyToClipboard } from '../utils/clipboard'; import { copyToClipboard } from '../utils/clipboard';
import { DeleteConfirmModal } from '../components/ui/DeleteConfirmModal';
// Import our service layer and types // Import our service layer and types
import { projectService } from '../services/projectService'; import { projectService } from '../services/projectService';
@@ -1062,76 +1063,3 @@ export function ProjectPage({
); );
} }
// Reusable Delete Confirmation Modal Component
export interface DeleteConfirmModalProps {
itemName: string;
onConfirm: () => void;
onCancel: () => void;
type: 'project' | 'task' | 'client';
}
export const DeleteConfirmModal: React.FC<DeleteConfirmModalProps> = ({ itemName, onConfirm, onCancel, type }) => {
const getTitle = () => {
switch (type) {
case 'project': return 'Delete Project';
case 'task': return 'Delete Task';
case 'client': return 'Delete MCP Client';
}
};
const getMessage = () => {
switch (type) {
case 'project': return `Are you sure you want to delete the "${itemName}" project? This will also delete all associated tasks and documents and cannot be undone.`;
case 'task': return `Are you sure you want to delete the "${itemName}" task? This action cannot be undone.`;
case 'client': return `Are you sure you want to delete the "${itemName}" client? This will permanently remove its configuration and cannot be undone.`;
}
};
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
<div className="relative p-6 rounded-md backdrop-blur-md w-full max-w-md
bg-gradient-to-b from-white/80 to-white/60 dark:from-white/10 dark:to-black/30
border border-gray-200 dark:border-zinc-800/50
shadow-[0_10px_30px_-15px_rgba(0,0,0,0.1)] dark:shadow-[0_10px_30px_-15px_rgba(0,0,0,0.7)]
before:content-[''] before:absolute before:top-0 before:left-0 before:right-0 before:h-[2px]
before:rounded-t-[4px] before:bg-red-500
before:shadow-[0_0_10px_2px_rgba(239,68,68,0.4)] dark:before:shadow-[0_0_20px_5px_rgba(239,68,68,0.7)]">
<div className="relative z-10">
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<Trash2 className="w-6 h-6 text-red-600 dark:text-red-400" />
</div>
<div>
<h3 className="text-lg font-semibold text-gray-800 dark:text-white">
{getTitle()}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
This action cannot be undone
</p>
</div>
</div>
<p className="text-gray-700 dark:text-gray-300 mb-6">
{getMessage()}
</p>
<div className="flex justify-end gap-3">
<button
onClick={onCancel}
className="px-4 py-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors"
>
Cancel
</button>
<button
onClick={onConfirm}
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors shadow-lg shadow-red-600/25 hover:shadow-red-700/25"
>
Delete
</button>
</div>
</div>
</div>
</div>
);
};