mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-05 09:33:07 +00:00
Merge pull request #274 from illia1f/fix/ui-trash-operations
fix: replace window.confirm with React dialogs in trash operations
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
||||
useNavigation,
|
||||
useProjectCreation,
|
||||
useSetupDialog,
|
||||
useTrashDialog,
|
||||
useTrashOperations,
|
||||
useProjectTheme,
|
||||
useUnviewedValidations,
|
||||
} from './sidebar/hooks';
|
||||
@@ -68,6 +68,9 @@ export function Sidebar() {
|
||||
// State for delete project confirmation dialog
|
||||
const [showDeleteProjectDialog, setShowDeleteProjectDialog] = useState(false);
|
||||
|
||||
// State for trash dialog
|
||||
const [showTrashDialog, setShowTrashDialog] = useState(false);
|
||||
|
||||
// Project theme management (must come before useProjectCreation which uses globalTheme)
|
||||
const { globalTheme } = useProjectTheme();
|
||||
|
||||
@@ -131,20 +134,17 @@ export function Sidebar() {
|
||||
// Unviewed validations count
|
||||
const { count: unviewedValidationsCount } = useUnviewedValidations(currentProject);
|
||||
|
||||
// Trash dialog and operations
|
||||
// Trash operations
|
||||
const {
|
||||
showTrashDialog,
|
||||
setShowTrashDialog,
|
||||
activeTrashId,
|
||||
isEmptyingTrash,
|
||||
handleRestoreProject,
|
||||
handleDeleteProjectFromDisk,
|
||||
handleEmptyTrash,
|
||||
} = useTrashDialog({
|
||||
} = useTrashOperations({
|
||||
restoreTrashedProject,
|
||||
deleteTrashedProject,
|
||||
emptyTrash,
|
||||
trashedProjects,
|
||||
});
|
||||
|
||||
// Spec regeneration events
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { X, Trash2, Undo2 } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -8,6 +9,8 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog';
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import type { TrashedProject } from '@/lib/electron';
|
||||
|
||||
interface TrashDialogProps {
|
||||
@@ -33,8 +36,42 @@ export function TrashDialog({
|
||||
handleEmptyTrash,
|
||||
isEmptyingTrash,
|
||||
}: TrashDialogProps) {
|
||||
// Confirmation dialog state (managed internally to avoid prop drilling)
|
||||
const [deleteFromDiskProject, setDeleteFromDiskProject] = useState<TrashedProject | null>(null);
|
||||
const [showEmptyTrashConfirm, setShowEmptyTrashConfirm] = useState(false);
|
||||
|
||||
// Reset confirmation dialog state when main dialog closes
|
||||
const handleOpenChange = (isOpen: boolean) => {
|
||||
if (!isOpen) {
|
||||
setDeleteFromDiskProject(null);
|
||||
setShowEmptyTrashConfirm(false);
|
||||
}
|
||||
onOpenChange(isOpen);
|
||||
};
|
||||
|
||||
const onDeleteFromDiskClick = (project: TrashedProject) => {
|
||||
setDeleteFromDiskProject(project);
|
||||
};
|
||||
|
||||
const onConfirmDeleteFromDisk = () => {
|
||||
if (deleteFromDiskProject) {
|
||||
handleDeleteProjectFromDisk(deleteFromDiskProject);
|
||||
setDeleteFromDiskProject(null);
|
||||
}
|
||||
};
|
||||
|
||||
const onEmptyTrashClick = () => {
|
||||
setShowEmptyTrashConfirm(true);
|
||||
};
|
||||
|
||||
const onConfirmEmptyTrash = () => {
|
||||
handleEmptyTrash();
|
||||
setShowEmptyTrashConfirm(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="bg-popover/95 backdrop-blur-xl border-border max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Recycle Bin</DialogTitle>
|
||||
@@ -72,7 +109,7 @@ export function TrashDialog({
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => handleDeleteProjectFromDisk(project)}
|
||||
onClick={() => onDeleteFromDiskClick(project)}
|
||||
disabled={activeTrashId === project.id}
|
||||
data-testid={`delete-project-disk-${project.id}`}
|
||||
>
|
||||
@@ -102,7 +139,7 @@ export function TrashDialog({
|
||||
{trashedProjects.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleEmptyTrash}
|
||||
onClick={onEmptyTrashClick}
|
||||
disabled={isEmptyingTrash}
|
||||
data-testid="empty-trash"
|
||||
>
|
||||
@@ -112,5 +149,33 @@ export function TrashDialog({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete from disk confirmation dialog */}
|
||||
{deleteFromDiskProject && (
|
||||
<DeleteConfirmDialog
|
||||
open
|
||||
onOpenChange={(isOpen) => !isOpen && setDeleteFromDiskProject(null)}
|
||||
onConfirm={onConfirmDeleteFromDisk}
|
||||
title={`Delete "${deleteFromDiskProject.name}" from disk?`}
|
||||
description="This sends the folder to your system Trash."
|
||||
confirmText="Delete from disk"
|
||||
testId="delete-from-disk-confirm-dialog"
|
||||
confirmTestId="confirm-delete-from-disk-button"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Empty trash confirmation dialog */}
|
||||
<ConfirmDialog
|
||||
open={showEmptyTrashConfirm}
|
||||
onOpenChange={setShowEmptyTrashConfirm}
|
||||
onConfirm={onConfirmEmptyTrash}
|
||||
title="Empty Recycle Bin"
|
||||
description="Clear all projects from recycle bin? This does not delete folders from disk."
|
||||
confirmText="Empty"
|
||||
confirmVariant="destructive"
|
||||
icon={Trash2}
|
||||
iconClassName="text-destructive"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,5 @@ export { useSpecRegeneration } from './use-spec-regeneration';
|
||||
export { useNavigation } from './use-navigation';
|
||||
export { useProjectCreation } from './use-project-creation';
|
||||
export { useSetupDialog } from './use-setup-dialog';
|
||||
export { useTrashDialog } from './use-trash-dialog';
|
||||
export { useProjectTheme } from './use-project-theme';
|
||||
export { useUnviewedValidations } from './use-unviewed-validations';
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { useTrashOperations } from './use-trash-operations';
|
||||
import type { TrashedProject } from '@/lib/electron';
|
||||
|
||||
interface UseTrashDialogProps {
|
||||
restoreTrashedProject: (projectId: string) => void;
|
||||
deleteTrashedProject: (projectId: string) => void;
|
||||
emptyTrash: () => void;
|
||||
trashedProjects: TrashedProject[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that combines trash operations with dialog state management
|
||||
*/
|
||||
export function useTrashDialog({
|
||||
restoreTrashedProject,
|
||||
deleteTrashedProject,
|
||||
emptyTrash,
|
||||
trashedProjects,
|
||||
}: UseTrashDialogProps) {
|
||||
// Dialog state
|
||||
const [showTrashDialog, setShowTrashDialog] = useState(false);
|
||||
|
||||
// Reuse existing trash operations logic
|
||||
const trashOperations = useTrashOperations({
|
||||
restoreTrashedProject,
|
||||
deleteTrashedProject,
|
||||
emptyTrash,
|
||||
trashedProjects,
|
||||
});
|
||||
|
||||
return {
|
||||
// Dialog state
|
||||
showTrashDialog,
|
||||
setShowTrashDialog,
|
||||
|
||||
// Trash operations (spread from existing hook)
|
||||
...trashOperations,
|
||||
};
|
||||
}
|
||||
@@ -6,35 +6,35 @@ interface UseTrashOperationsProps {
|
||||
restoreTrashedProject: (projectId: string) => void;
|
||||
deleteTrashedProject: (projectId: string) => void;
|
||||
emptyTrash: () => void;
|
||||
trashedProjects: TrashedProject[];
|
||||
}
|
||||
|
||||
export function useTrashOperations({
|
||||
restoreTrashedProject,
|
||||
deleteTrashedProject,
|
||||
emptyTrash,
|
||||
trashedProjects,
|
||||
}: UseTrashOperationsProps) {
|
||||
const [activeTrashId, setActiveTrashId] = useState<string | null>(null);
|
||||
const [isEmptyingTrash, setIsEmptyingTrash] = useState(false);
|
||||
|
||||
const handleRestoreProject = useCallback(
|
||||
(projectId: string) => {
|
||||
try {
|
||||
restoreTrashedProject(projectId);
|
||||
toast.success('Project restored', {
|
||||
description: 'Added back to your project list.',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Sidebar] Failed to restore project:', error);
|
||||
toast.error('Failed to restore project', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
},
|
||||
[restoreTrashedProject]
|
||||
);
|
||||
|
||||
const handleDeleteProjectFromDisk = useCallback(
|
||||
async (trashedProject: TrashedProject) => {
|
||||
const confirmed = window.confirm(
|
||||
`Delete "${trashedProject.name}" from disk?\nThis sends the folder to your system Trash.`
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
setActiveTrashId(trashedProject.id);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
@@ -64,23 +64,19 @@ export function useTrashOperations({
|
||||
);
|
||||
|
||||
const handleEmptyTrash = useCallback(() => {
|
||||
if (trashedProjects.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = window.confirm(
|
||||
'Clear all projects from recycle bin? This does not delete folders from disk.'
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
setIsEmptyingTrash(true);
|
||||
try {
|
||||
emptyTrash();
|
||||
toast.success('Recycle bin cleared');
|
||||
} catch (error) {
|
||||
console.error('[Sidebar] Failed to empty trash:', error);
|
||||
toast.error('Failed to clear recycle bin', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
} finally {
|
||||
setIsEmptyingTrash(false);
|
||||
}
|
||||
}, [emptyTrash, trashedProjects.length]);
|
||||
}, [emptyTrash]);
|
||||
|
||||
return {
|
||||
activeTrashId,
|
||||
|
||||
Reference in New Issue
Block a user