From 7805daf92533ebc4c7fa75aadb13a08f7bd7e947 Mon Sep 17 00:00:00 2001 From: eshurakov <54751+eshurakov@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:33:13 +0000 Subject: [PATCH] feat(cloud-agent-next): add deletion confirmation and loading state for sessions Introduces an `AlertDialog` to confirm session deletion before proceeding, preventing accidental deletions. Added a loading spinner and disabled interaction for the specific session row while a deletion request is in progress to improve UX and prevent duplicate requests. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../cloud-agent-next/ChatSidebar.tsx | 22 ++++- .../cloud-agent-next/CloudSidebarLayout.tsx | 80 +++++++++++++++---- 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/apps/web/src/components/cloud-agent-next/ChatSidebar.tsx b/apps/web/src/components/cloud-agent-next/ChatSidebar.tsx index 96f377d05a..b54abd42a1 100644 --- a/apps/web/src/components/cloud-agent-next/ChatSidebar.tsx +++ b/apps/web/src/components/cloud-agent-next/ChatSidebar.tsx @@ -9,6 +9,7 @@ import { Trash2, X, Pencil, + LoaderCircle, } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { TimeAgo } from '@/components/shared/TimeAgo'; @@ -107,6 +108,7 @@ type ChatSidebarProps = { currentSessionId?: string; organizationId?: string; onDeleteSession?: (sessionId: string) => void; + deletingSessionId?: string; onRenameSession?: (sessionId: string, title: string) => Promise; isInSheet?: boolean; activeSessions?: ActiveSession[]; @@ -125,6 +127,7 @@ function SessionRow({ isActive, isLive, onDeleteSession, + isDeleting, onStartRename, isEditing, editTitle, @@ -137,6 +140,7 @@ function SessionRow({ isActive: boolean; isLive: boolean; onDeleteSession?: (sessionId: string) => void; + isDeleting: boolean; onStartRename?: () => void; isEditing: boolean; editTitle: string; @@ -179,11 +183,12 @@ function SessionRow({ return (
setHovered(true)} onMouseLeave={() => setHovered(false)} className={cn( 'hover:bg-accent cursor-pointer rounded-lg text-sm transition-colors', + isDeleting && 'cursor-wait opacity-60', isActive && 'bg-accent font-medium' )} > @@ -202,7 +207,12 @@ function SessionRow({ {session.prompt} - {shouldReplaceTime ? ( + {isDeleting ? ( + + ) : shouldReplaceTime ? ( )} - {(onDeleteSession || onStartRename) && ( + {!isDeleting && (onDeleteSession || onStartRename) && ( onDeleteSession(session.sessionId)} + onClick={e => { + e.stopPropagation(); + onDeleteSession(session.sessionId); + }} > Delete session @@ -594,6 +607,7 @@ export function ChatSidebar({ isLive={activeSessionIds.has(session.sessionId)} onDeleteSession={onDeleteSession} onStartRename={onRenameSession ? () => handleStartRename(session) : undefined} + isDeleting={deletingSessionId === session.sessionId} isEditing={editingSessionId === session.sessionId} editTitle={editTitle} onEditTitleChange={setEditTitle} diff --git a/apps/web/src/components/cloud-agent-next/CloudSidebarLayout.tsx b/apps/web/src/components/cloud-agent-next/CloudSidebarLayout.tsx index 35557d5323..18b2c0c0d4 100644 --- a/apps/web/src/components/cloud-agent-next/CloudSidebarLayout.tsx +++ b/apps/web/src/components/cloud-agent-next/CloudSidebarLayout.tsx @@ -13,6 +13,16 @@ import { useSidebarSessions } from './hooks/useSidebarSessions'; import { useActiveSessions } from './hooks/useActiveSessions'; import { deleteSessionFromStoreAtom } from './store/db-session-atoms'; import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; import { useLocalStorage } from '@/hooks/useLocalStorage'; // Context for children to toggle the mobile sidebar sheet @@ -49,6 +59,8 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay initializeWithValue: false, }); const [mobileSheetOpen, setMobileSheetOpen] = useState(false); + const [deletingSessionId, setDeletingSessionId] = useState(); + const [sessionPendingDeletion, setSessionPendingDeletion] = useState(); const repoUpdatedSince = useMemo(() => startOfDay(subDays(new Date(), 30)).toISOString(), []); const createdOnPlatform = useMemo(() => { @@ -107,30 +119,32 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay const handleDeleteSession = useCallback( async (sessionId: string) => { - // Navigate away if deleting the current session - if (sessionId === currentSessionId) { - const basePath = organizationId ? `/organizations/${organizationId}/cloud` : '/cloud'; - router.push(basePath); + setDeletingSessionId(sessionId); + try { + await deleteCliSessionV2({ session_id: sessionId }); + } catch (error) { + console.error('Error calling session deletion API:', error); + toast.error('Failed to delete session'); + return false; + } finally { + setDeletingSessionId(undefined); } - // Delete from IndexedDB (optimistic) try { await deleteSessionFromStore(sessionId); } catch (error) { console.error('Error deleting session from IndexedDB:', error); } - // Delete from server - try { - await deleteCliSessionV2({ session_id: sessionId }); - toast('Session deleted successfully'); - } catch (error) { - console.error('Error calling session deletion API:', error); - toast.error('Failed to delete session'); + if (sessionId === currentSessionId) { + const basePath = organizationId ? `/organizations/${organizationId}/cloud` : '/cloud'; + router.push(basePath); } + toast('Session deleted successfully'); void queryClient.invalidateQueries(trpc.cliSessionsV2.list.pathFilter()); refetchSessions(); + return true; }, [ currentSessionId, @@ -144,6 +158,15 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay ] ); + const handleConfirmDelete = useCallback(async () => { + if (!sessionPendingDeletion) return; + + const wasDeleted = await handleDeleteSession(sessionPendingDeletion); + if (wasDeleted) { + setSessionPendingDeletion(undefined); + } + }, [handleDeleteSession, sessionPendingDeletion]); + const handleRenameSession = useCallback( async (sessionId: string, title: string) => { await renameCliSessionV2({ session_id: sessionId, title }); @@ -170,7 +193,8 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay sessions={sessions} currentSessionId={currentSessionId} organizationId={organizationId} - onDeleteSession={handleDeleteSession} + onDeleteSession={setSessionPendingDeletion} + deletingSessionId={deletingSessionId} onRenameSession={handleRenameSession} isInSheet activeSessions={activeSessions} @@ -192,7 +216,8 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay sessions={sessions} currentSessionId={currentSessionId} organizationId={organizationId} - onDeleteSession={handleDeleteSession} + onDeleteSession={setSessionPendingDeletion} + deletingSessionId={deletingSessionId} onRenameSession={handleRenameSession} activeSessions={activeSessions} searchQuery={searchQuery} @@ -208,6 +233,33 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay {/* Main Content */}
{children}
+ { + if (!open && !deletingSessionId) { + setSessionPendingDeletion(undefined); + } + }} + > + + + Delete session? + + This permanently deletes the session and its history. + + + + Cancel + + {deletingSessionId ? 'Deleting...' : 'Delete session'} + + + + ); }