From 418afe00eda0f68f26c8d262e909ed2145aafe07 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Fri, 6 Mar 2026 08:14:44 -0800 Subject: [PATCH] Move project remove confirmation from inline buttons to modal popup Replaces the inline Yes/No confirmation with a proper ConfirmRemoveModal dialog, consistent with other modal patterns in the app (EnvVarsModal, etc.). Supports Escape key and overlay click to dismiss. Co-Authored-By: Claude Opus 4.6 --- .../projects/ConfirmRemoveModal.tsx | 55 +++++++++++++++++++ app/src/components/projects/ProjectCard.tsx | 48 +++++++--------- 2 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 app/src/components/projects/ConfirmRemoveModal.tsx diff --git a/app/src/components/projects/ConfirmRemoveModal.tsx b/app/src/components/projects/ConfirmRemoveModal.tsx new file mode 100644 index 0000000..d9d99b9 --- /dev/null +++ b/app/src/components/projects/ConfirmRemoveModal.tsx @@ -0,0 +1,55 @@ +import { useEffect, useRef, useCallback } from "react"; + +interface Props { + projectName: string; + onConfirm: () => void; + onCancel: () => void; +} + +export default function ConfirmRemoveModal({ projectName, onConfirm, onCancel }: Props) { + const overlayRef = useRef(null); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onCancel(); + }; + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [onCancel]); + + const handleOverlayClick = useCallback( + (e: React.MouseEvent) => { + if (e.target === overlayRef.current) onCancel(); + }, + [onCancel], + ); + + return ( +
+
+

Remove Project

+

+ Are you sure you want to remove {projectName}? This will delete the container, config volume, and stored credentials. +

+
+ + +
+
+
+ ); +} diff --git a/app/src/components/projects/ProjectCard.tsx b/app/src/components/projects/ProjectCard.tsx index 5b7a648..2464986 100644 --- a/app/src/components/projects/ProjectCard.tsx +++ b/app/src/components/projects/ProjectCard.tsx @@ -11,6 +11,7 @@ import PortMappingsModal from "./PortMappingsModal"; import ClaudeInstructionsModal from "./ClaudeInstructionsModal"; import ContainerProgressModal from "./ContainerProgressModal"; import FileManagerModal from "./FileManagerModal"; +import ConfirmRemoveModal from "./ConfirmRemoveModal"; interface Props { project: Project; @@ -32,7 +33,7 @@ export default function ProjectCard({ project }: Props) { const [progressMsg, setProgressMsg] = useState(null); const [activeOperation, setActiveOperation] = useState<"starting" | "stopping" | "resetting" | null>(null); const [operationCompleted, setOperationCompleted] = useState(false); - const [showRemoveConfirm, setShowRemoveConfirm] = useState(false); + const [showRemoveModal, setShowRemoveModal] = useState(false); const [isEditingName, setIsEditingName] = useState(false); const [editName, setEditName] = useState(project.name); const isSelected = selectedProjectId === project.id; @@ -435,34 +436,12 @@ export default function ProjectCard({ project }: Props) { disabled={false} label={showConfig ? "Hide" : "Config"} /> - {showRemoveConfirm ? ( - - Remove? - - - - ) : ( - setShowRemoveConfirm(true)} - disabled={loading} - label="Remove" - danger - /> - )} + setShowRemoveModal(true)} + disabled={loading} + label="Remove" + danger + /> {/* Config panel */} @@ -925,6 +904,17 @@ export default function ProjectCard({ project }: Props) { /> )} + {showRemoveModal && ( + { + setShowRemoveModal(false); + await remove(project.id); + }} + onCancel={() => setShowRemoveModal(false)} + /> + )} + {activeOperation && (