Skip to content

Commit b4c8a0c

Browse files
committed
Adds conversation deletion functionality to chat sidebar
Implements permanent conversation deletion with confirmation dialog and safety checks. Prevents deletion of note-to-self conversations and requires user confirmation before proceeding. Automatically clears active conversation if deleted and refreshes the conversation list to reflect changes. Includes proper error handling and user feedback for failed deletion attempts.
1 parent 7e765e0 commit b4c8a0c

File tree

4 files changed

+175
-0
lines changed

4 files changed

+175
-0
lines changed

src/lib/components/ChatSidebar.svelte

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,49 @@
166166
}
167167
}
168168
169+
// Handle delete conversation action
170+
async function handleDeleteConversation(conversation) {
171+
contextMenu.show = false;
172+
173+
// Prevent deleting note-to-self conversations
174+
if (conversation.type === 'note_to_self') {
175+
console.log('Cannot delete note-to-self conversations');
176+
return;
177+
}
178+
179+
// Confirm deletion
180+
const conversationName = conversation.name || conversation.conversation_name || 'this conversation';
181+
const confirmed = confirm(
182+
`Are you sure you want to permanently delete ${conversationName}?\n\n` +
183+
`This will delete all messages and files for all participants and cannot be undone.`
184+
);
185+
186+
if (!confirmed) {
187+
return;
188+
}
189+
190+
try {
191+
const result = await chat.deleteConversation(conversation.id);
192+
193+
if (result.success) {
194+
// If the deleted conversation was active, clear it
195+
if (activeConversationId === conversation.id) {
196+
onConversationSelect(null);
197+
}
198+
199+
// Reload conversations to reflect changes
200+
hasLoadedConversations = false;
201+
await loadConversationsData();
202+
} else {
203+
console.error('Failed to delete conversation:', result.error);
204+
alert(`Failed to delete conversation: ${result.error}`);
205+
}
206+
} catch (error) {
207+
console.error('Delete conversation error:', error);
208+
alert('Failed to delete conversation. Please try again.');
209+
}
210+
}
211+
169212
// Handle conversation selection
170213
function handleConversationSelect(/** @type {string} */ conversationId) {
171214
onConversationSelect(conversationId);
@@ -496,6 +539,15 @@
496539
</svg>
497540
{contextMenu.conversation?.is_archived ? 'Unarchive' : 'Archive'}
498541
</button>
542+
<button
543+
class="context-item delete-item"
544+
onclick={() => handleDeleteConversation(contextMenu.conversation)}
545+
>
546+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
547+
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
548+
</svg>
549+
Delete
550+
</button>
499551
</div>
500552
{/if}
501553
@@ -974,4 +1026,13 @@
9741026
.context-item:hover {
9751027
background: var(--color-bg-secondary);
9761028
}
1029+
1030+
.context-item.delete-item {
1031+
color: var(--color-error);
1032+
}
1033+
1034+
.context-item.delete-item:hover {
1035+
background: var(--color-error);
1036+
color: white;
1037+
}
9771038
</style>

src/lib/stores/chat.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,13 @@ function createChatStore() {
644644
const unarchiveConversation = (conversationId) =>
645645
conversationUtils.unarchiveConversation(apiPost, loadConversations, conversationId);
646646

647+
/**
648+
* Delete a conversation permanently
649+
* @param {string} conversationId - Conversation ID
650+
*/
651+
const deleteConversation = (conversationId) =>
652+
conversationUtils.deleteConversation(apiPost, loadConversations, conversationId);
653+
647654
return {
648655
subscribe,
649656

@@ -658,6 +665,7 @@ function createChatStore() {
658665
createConversation,
659666
archiveConversation,
660667
unarchiveConversation,
668+
deleteConversation,
661669

662670
// Message management
663671
loadMessages,

src/lib/utils/conversation-utils.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,26 @@ export async function unarchiveConversation(apiPost, loadConversations, conversa
4545
console.error('Failed to unarchive conversation:', error);
4646
return { success: false, error: 'Failed to unarchive conversation' };
4747
}
48+
}
49+
50+
/**
51+
* Delete a conversation permanently
52+
* @param {Function} apiPost - API POST function (from chat store)
53+
* @param {Function} loadConversations - Function to reload conversations
54+
* @param {string} conversationId - Conversation ID
55+
* @returns {Promise<Object>} Result object with success status
56+
*/
57+
export async function deleteConversation(apiPost, loadConversations, conversationId) {
58+
try {
59+
const response = await apiPost('/api/conversations/delete', { conversationId });
60+
61+
if (response.success) {
62+
await loadConversations();
63+
return { success: true };
64+
}
65+
return { success: false, error: response.error || 'Failed to delete conversation' };
66+
} catch (error) {
67+
console.error('Failed to delete conversation:', error);
68+
return { success: false, error: 'Failed to delete conversation' };
69+
}
4870
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* @fileoverview Delete Conversation API Endpoint
3+
* Handles permanently deleting a conversation and all associated data for all participants
4+
*/
5+
6+
import { json } from '@sveltejs/kit';
7+
import { withAuth } from '$lib/api/middleware/auth.js';
8+
9+
export const POST = withAuth(async ({ request, locals }) => {
10+
try {
11+
const { conversationId } = await request.json();
12+
13+
if (!conversationId) {
14+
return json({ error: 'Missing conversationId' }, { status: 400 });
15+
}
16+
17+
const { supabase, user: authUser } = locals;
18+
19+
// First, verify the user is a participant in this conversation
20+
const { data: participant, error: participantError } = await supabase
21+
.from('conversation_participants')
22+
.select('id')
23+
.eq('conversation_id', conversationId)
24+
.eq('user_id', authUser.id)
25+
.single();
26+
27+
if (participantError || !participant) {
28+
console.error('User not a participant:', participantError);
29+
return json({
30+
error: 'Conversation not found or you are not a participant'
31+
}, { status: 404 });
32+
}
33+
34+
// Delete all messages in the conversation
35+
const { error: messagesError } = await supabase
36+
.from('messages')
37+
.delete()
38+
.eq('conversation_id', conversationId);
39+
40+
if (messagesError) {
41+
console.error('Error deleting messages:', messagesError);
42+
return json({ error: 'Failed to delete messages' }, { status: 500 });
43+
}
44+
45+
// Delete all file attachments associated with the conversation
46+
const { error: filesError } = await supabase
47+
.from('file_attachments')
48+
.delete()
49+
.eq('conversation_id', conversationId);
50+
51+
if (filesError) {
52+
console.error('Error deleting file attachments:', filesError);
53+
// Continue even if file deletion fails
54+
}
55+
56+
// Delete all conversation participants
57+
const { error: participantsError } = await supabase
58+
.from('conversation_participants')
59+
.delete()
60+
.eq('conversation_id', conversationId);
61+
62+
if (participantsError) {
63+
console.error('Error deleting participants:', participantsError);
64+
return json({ error: 'Failed to delete participants' }, { status: 500 });
65+
}
66+
67+
// Finally, delete the conversation itself
68+
const { error: conversationError } = await supabase
69+
.from('conversations')
70+
.delete()
71+
.eq('id', conversationId);
72+
73+
if (conversationError) {
74+
console.error('Error deleting conversation:', conversationError);
75+
return json({ error: 'Failed to delete conversation' }, { status: 500 });
76+
}
77+
78+
console.log(`✅ Successfully deleted conversation ${conversationId} and all associated data`);
79+
return json({ success: true });
80+
} catch (error) {
81+
console.error('Delete conversation error:', error);
82+
return json({ error: 'Internal server error' }, { status: 500 });
83+
}
84+
});

0 commit comments

Comments
 (0)