Skip to content

Commit a29e143

Browse files
committed
fix: type repo name to confirm delete -- prevents accidental deletion
Both delete dialogs (card grid + detail view) now require typing the exact repo name before the Delete button enables. Shared DeleteConfirmDialog component: - Shows repo name in bold - Input field with placeholder matching repo name - Delete button disabled until text matches exactly - Button text: 'Delete opencodeintel' (includes name) - Input clears on cancel or close Reused in RepoList (card grid) and RepoDetailView (detail header).
1 parent 27936ba commit a29e143

2 files changed

Lines changed: 71 additions & 52 deletions

File tree

frontend/src/components/RepoList.tsx

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
DialogTitle,
1818
} from './ui/dialog'
1919
import { Button } from './ui/button'
20+
import { Input } from './ui/input'
2021
import type { Repository } from '../types'
2122
import { RepoGridSkeleton } from './ui/Skeleton'
2223

@@ -269,30 +270,69 @@ export function RepoList({ repos, selectedRepo, onSelect, onDelete, onAddClick,
269270
</div>
270271

271272
{/* Delete confirmation dialog */}
272-
<Dialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>
273-
<DialogContent>
274-
<DialogHeader>
275-
<DialogTitle>Delete repository</DialogTitle>
276-
<DialogDescription>
277-
This will permanently remove <strong>{deleteTarget?.name}</strong> and all its indexed data. This action cannot be undone.
278-
</DialogDescription>
279-
</DialogHeader>
280-
<DialogFooter className="gap-2">
281-
<Button variant="outline" onClick={() => setDeleteTarget(null)}>Cancel</Button>
282-
<Button
283-
variant="destructive"
284-
onClick={() => {
285-
if (deleteTarget && onDelete) {
286-
onDelete(deleteTarget.id)
287-
setDeleteTarget(null)
288-
}
289-
}}
290-
>
291-
Delete
292-
</Button>
293-
</DialogFooter>
294-
</DialogContent>
295-
</Dialog>
273+
<DeleteConfirmDialog
274+
repo={deleteTarget}
275+
onCancel={() => setDeleteTarget(null)}
276+
onConfirm={() => {
277+
if (deleteTarget && onDelete) {
278+
onDelete(deleteTarget.id)
279+
setDeleteTarget(null)
280+
}
281+
}}
282+
/>
296283
</div>
297284
)
298285
}
286+
287+
288+
export function DeleteConfirmDialog({
289+
repo,
290+
onCancel,
291+
onConfirm,
292+
}: {
293+
repo: Repository | null
294+
onCancel: () => void
295+
onConfirm: () => void
296+
}) {
297+
const [confirmText, setConfirmText] = useState('')
298+
const repoName = repo?.name || ''
299+
const isMatch = confirmText === repoName
300+
301+
return (
302+
<Dialog
303+
open={!!repo}
304+
onOpenChange={(open) => { if (!open) { setConfirmText(''); onCancel() } }}
305+
>
306+
<DialogContent>
307+
<DialogHeader>
308+
<DialogTitle>Delete repository</DialogTitle>
309+
<DialogDescription>
310+
This will permanently remove <strong>{repoName}</strong> and all its
311+
indexed data. This action cannot be undone.
312+
</DialogDescription>
313+
</DialogHeader>
314+
<div className="py-2">
315+
<p className="text-sm text-muted-foreground mb-2">
316+
Type <strong className="text-foreground">{repoName}</strong> to confirm
317+
</p>
318+
<Input
319+
value={confirmText}
320+
onChange={(e) => setConfirmText(e.target.value)}
321+
placeholder={repoName}
322+
autoFocus
323+
/>
324+
</div>
325+
<DialogFooter className="gap-2">
326+
<Button variant="outline" onClick={() => { setConfirmText(''); onCancel() }}>Cancel</Button>
327+
<Button
328+
variant="destructive"
329+
disabled={!isMatch}
330+
onClick={() => { setConfirmText(''); onConfirm() }}
331+
>
332+
Delete {repoName}
333+
</Button>
334+
</DialogFooter>
335+
</DialogContent>
336+
</Dialog>
337+
)
338+
}

frontend/src/components/dashboard/RepoDetailView.tsx

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,7 @@ import {
2121
DropdownMenuItem,
2222
DropdownMenuTrigger,
2323
} from '@/components/ui/dropdown-menu'
24-
import {
25-
Dialog,
26-
DialogContent,
27-
DialogDescription,
28-
DialogFooter,
29-
DialogHeader,
30-
DialogTitle,
31-
} from '@/components/ui/dialog'
32-
import { Button } from '@/components/ui/button'
24+
import { DeleteConfirmDialog } from '../RepoList'
3325
import { SearchPanel } from '../SearchPanel'
3426
import { DependencyGraph } from '../DependencyGraph'
3527
import { RepoOverview } from '../RepoOverview'
@@ -68,6 +60,7 @@ export function RepoDetailView({
6860
onDelete,
6961
}: RepoDetailViewProps) {
7062
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
63+
const deleteRepo = showDeleteDialog ? repo : null
7164
return (
7265
<motion.div
7366
key="repo-detail"
@@ -123,25 +116,11 @@ export function RepoDetailView({
123116
)}
124117
</div>
125118

126-
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
127-
<DialogContent>
128-
<DialogHeader>
129-
<DialogTitle>Delete repository</DialogTitle>
130-
<DialogDescription>
131-
This will permanently remove <strong>{repo.name}</strong> and all its indexed data. This action cannot be undone.
132-
</DialogDescription>
133-
</DialogHeader>
134-
<DialogFooter className="gap-2">
135-
<Button variant="outline" onClick={() => setShowDeleteDialog(false)}>Cancel</Button>
136-
<Button
137-
variant="destructive"
138-
onClick={() => { setShowDeleteDialog(false); onDelete?.() }}
139-
>
140-
Delete
141-
</Button>
142-
</DialogFooter>
143-
</DialogContent>
144-
</Dialog>
119+
<DeleteConfirmDialog
120+
repo={deleteRepo}
121+
onCancel={() => setShowDeleteDialog(false)}
122+
onConfirm={() => { setShowDeleteDialog(false); onDelete?.() }}
123+
/>
145124
</div>
146125

147126
{/* tab bar */}

0 commit comments

Comments
 (0)