feat(ux): accessible confirm dialog replaces window.confirm

- new components/ui/confirm.tsx: ConfirmProvider + useConfirm() built
  on the existing Radix alert-dialog (focus trap, ESC, aria for free)
- wired once into the app layout
- all 13 native window.confirm() delete/remove guards now use the
  styled, accessible confirm with a destructive action button
This commit is contained in:
Moheeb Zara
2026-05-15 22:18:15 -07:00
parent 64d7d626db
commit 1db9434930
14 changed files with 132 additions and 13 deletions
+5 -2
View File
@@ -12,6 +12,7 @@ import { createClient } from '@/lib/supabase/client'
import { toast } from 'sonner'
import type { Tables, TablesInsert } from '@/types/database'
import { PageTitle } from '@/components/ui/page-title'
import { useConfirm } from '@/components/ui/confirm'
import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyContent } from '@/components/ui/empty'
type KbEntry = Tables<'knowledge_base'>
@@ -355,6 +356,7 @@ function SecretRow({ secret, onDelete, canManageAcl, aclRoleOptions, aclInitial
aclRoleOptions?: { value: string; label: string }[]
aclInitial?: string[]
}) {
const confirm = useConfirm()
const [revealed, setRevealed] = useState(false)
const [value, setValue] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
@@ -371,7 +373,7 @@ function SecretRow({ secret, onDelete, canManageAcl, aclRoleOptions, aclInitial
}
async function handleDelete() {
if (!confirm(`Delete secret "${secret.title}"? This cannot be undone.`)) return
if (!(await confirm({ title: 'Delete secret', description: `"${secret.title}" cannot be undone.`, confirmText: 'Delete', destructive: true }))) return
const result = await deleteSecret(secret.id)
if (result.error) { toast.error(result.error); return }
toast.success('Secret deleted')
@@ -443,13 +445,14 @@ function KbEntryRow({
aclRoleOptions?: { value: string; label: string }[]
aclByEntity?: Record<string, string[]>
}) {
const confirm = useConfirm()
const [viewing, setViewing] = useState(false)
const aclEntityType: 'kb' | 'process' = entry.tags?.includes('process') ? 'process' : 'kb'
const aclInitial = aclByEntity?.[`${aclEntityType}:${entry.id}`] ?? []
async function handleDelete(e: React.MouseEvent) {
e.stopPropagation()
if (!confirm(`Delete "${entry.title}"?`)) return
if (!(await confirm({ title: 'Delete entry', description: `"${entry.title}" will be permanently removed.`, confirmText: 'Delete', destructive: true }))) return
const result = await deleteKbEntry(entry.id)
if ('error' in result && result.error) { toast.error(result.error); return }
toast.success('Entry deleted')