feat: replace browser confirm with gui modal for delete
All checks were successful
Automated Container Build / build-and-push (push) Successful in 40s

This commit is contained in:
Elijah 2026-05-23 10:52:06 -07:00
parent ee36e16dae
commit ee441dfefd

View file

@ -244,6 +244,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
const [draggedFile, setDraggedFile] = useState<string | null>(null);
const [uploads, setUploads] = useState<UploadProgress[]>([]);
const [toasts, setToasts] = useState<Toast[]>([]);
const [deleteConfirm, setDeleteConfirm] = useState<{ type: 'single' | 'multiple'; file?: FileItem; count?: number } | null>(null);
function showToast(message: string, type: 'success' | 'error' | 'info' = 'success') {
const id = Date.now() + Math.random();
@ -384,7 +385,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
e.preventDefault();
navigateUp();
} else if (e.key === 'Delete') {
deleteSelected();
requestDeleteSelected();
} else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
selectAll();
@ -613,18 +614,29 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
setSelectedFiles(new Set(files.map(f => f.path)));
}
async function deleteSelected() {
function requestDeleteSelected() {
if (selectedFiles.size === 0) return;
if (!confirm(`Move ${selectedFiles.size} items to trash?`)) return;
setDeleteConfirm({ type: 'multiple', count: selectedFiles.size });
}
async function executeDeleteSelected() {
const count = selectedFiles.size;
for (const path of selectedFiles) {
await api.deleteFile(path);
}
setSelectedFiles(new Set());
loadFiles(undefined, true);
setDeleteConfirm(null);
showToast(`Deleted ${count} item${count > 1 ? 's' : ''}`);
}
async function executeDeleteSingle(file: FileItem) {
await api.deleteFile(file.path);
loadFiles(undefined, true);
setDeleteConfirm(null);
showToast(`Deleted ${file.name}`);
}
async function handlePin(file: FileItem) {
await api.togglePin(file.path);
loadFiles(undefined, true);
@ -1578,7 +1590,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
<button
className="p-2 hover:bg-red-500/10 rounded-lg transition-colors flex items-center gap-2 text-sm text-red-500"
title="Delete Selected"
onClick={() => deleteSelected()}
onClick={() => requestDeleteSelected()}
>
<Trash2 className="w-4 h-4" />
</button>
@ -1693,12 +1705,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
label="Move to Trash"
danger
onClick={() => {
if (confirm(`Move ${contextMenu.file.name} to trash?`)) {
api.deleteFile(contextMenu.file.path).then(() => {
loadFiles(undefined, true);
showToast(`Deleted ${contextMenu.file.name}`);
});
}
setDeleteConfirm({ type: 'single', file: contextMenu.file });
setContextMenu(null);
}}
/>
@ -1857,6 +1864,47 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
)}
</AnimatePresence>
<AnimatePresence>
{deleteConfirm && (
<div className="fixed inset-0 z-[120] flex items-center justify-center bg-black/50 backdrop-blur-sm">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="w-full max-w-md overflow-hidden rounded-2xl shadow-xl"
style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)' }}
>
<div className="p-6">
<h2 className="text-xl font-bold mb-2" style={{ color: 'var(--color-text-primary)' }}>Move to Trash?</h2>
<p className="mb-6" style={{ color: 'var(--color-text-secondary)' }}>
{deleteConfirm.type === 'multiple'
? `Are you sure you want to move ${deleteConfirm.count} items to the trash?`
: `Are you sure you want to move ${deleteConfirm.file?.name} to the trash?`}
</p>
<div className="flex justify-end gap-3">
<button
onClick={() => setDeleteConfirm(null)}
className="px-4 py-2 rounded-lg text-sm font-medium transition-colors hover:opacity-80"
style={{ backgroundColor: 'var(--color-bg-tertiary)', color: 'var(--color-text-primary)' }}
>
Cancel
</button>
<button
onClick={() => {
if (deleteConfirm.type === 'multiple') executeDeleteSelected();
else if (deleteConfirm.file) executeDeleteSingle(deleteConfirm.file);
}}
className="px-4 py-2 rounded-lg text-sm font-medium transition-colors hover:opacity-90 bg-red-500 text-white"
>
Move to Trash
</button>
</div>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
<AnimatePresence>
{showTrashModal && (
<div className="fixed inset-0 z-[120] flex items-center justify-center bg-black/50 backdrop-blur-sm">