From 55e197a5c506a75de25c05e23e84a7bf60a7a783 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Wed, 10 Dec 2025 20:36:27 +0100 Subject: [PATCH] Add connection request approval flow and improve friends list UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Connection Request Approval: - Add 'Connection Requests' section showing pending incoming connections - Host must explicitly accept or deny incoming connection requests - Shows username before accepting (no auto-accept) - Accept: moves to active chats and sends acknowledgment - Decline: closes connection and removes from pending - Toast notification when someone wants to chat Friends List UX Improvements: - Add ⏸ (pause) button to close active chat without removing friend - Change ✕ to 🗑 (trash) icon for removing friends - Add confirmation dialog before removing a friend - Separate 'close chat' from 'remove friend' actions - Clearer visual distinction between actions This prevents accidental friend removal and gives hosts control over who they connect with before establishing the chat. --- src/App.jsx | 201 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 183 insertions(+), 18 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index fc22381..13906ff 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -90,6 +90,7 @@ export default function App() { const [activeChats, setActiveChats] = useState({}); const [selectedChat, setSelectedChat] = useState(null); const [messageInputs, setMessageInputs] = useState({}); + const [pendingRequests, setPendingRequests] = useState({}); // Pending incoming connection requests // Service - we publish one service that can accept multiple connections const [myServicePublished, setMyServicePublished] = useState(false); @@ -508,29 +509,24 @@ export default function App() { if (msg.type === 'identify') { // Peer identified themselves peerUsername = msg.from; - console.log(`📡 New chat connection from: ${peerUsername}`); + console.log(`📡 New connection request from: ${peerUsername}`); - setActiveChats(prev => ({ + // Add to pending requests for approval + setPendingRequests(prev => ({ ...prev, [peerUsername]: { username: peerUsername, channel: dc, connection: pc, - messages: prev[peerUsername]?.messages || [], - status: 'connected', - role: 'host' + timestamp: Date.now() } })); - // Auto-select the incoming chat to show it immediately - setSelectedChat(peerUsername); - toast.success(`${peerUsername} connected to you!`); - - // Send acknowledgment - dc.send(JSON.stringify({ - type: 'identify_ack', - from: hostUsername - })); + // Show notification + toast(`${peerUsername} wants to chat with you`, { + duration: 5000, + icon: '👤' + }); } else if (msg.type === 'message' && peerUsername) { // Chat message setActiveChats(prev => ({ @@ -785,6 +781,69 @@ export default function App() { })); }; + // Accept incoming connection request + const handleAcceptRequest = (username) => { + const request = pendingRequests[username]; + if (!request) return; + + console.log(`✅ Accepting connection request from: ${username}`); + + // Move from pending to active chats + setActiveChats(prev => ({ + ...prev, + [username]: { + username: username, + channel: request.channel, + connection: request.connection, + messages: prev[username]?.messages || [], + status: 'connected', + role: 'host' + } + })); + + // Remove from pending + setPendingRequests(prev => { + const updated = { ...prev }; + delete updated[username]; + return updated; + }); + + // Send acknowledgment + request.channel.send(JSON.stringify({ + type: 'identify_ack', + from: myUsername + })); + + // Auto-select the chat + setSelectedChat(username); + toast.success(`Connected with ${username}!`); + }; + + // Deny incoming connection request + const handleDenyRequest = (username) => { + const request = pendingRequests[username]; + if (!request) return; + + console.log(`❌ Denying connection request from: ${username}`); + + // Close the connection + try { + request.channel.close(); + request.connection.close(); + } catch (err) { + console.error('Error closing connection:', err); + } + + // Remove from pending + setPendingRequests(prev => { + const updated = { ...prev }; + delete updated[username]; + return updated; + }); + + toast.success(`Declined request from ${username}`); + }; + // Send message const handleSendMessage = (contact) => { const text = messageInputs[contact]; @@ -977,11 +1036,81 @@ export default function App() { + {/* Pending Connection Requests */} + {Object.keys(pendingRequests).length > 0 && ( +
+
+ Connection Requests ({Object.keys(pendingRequests).length}) +
+ {Object.keys(pendingRequests).map(username => ( +
+
+
+ {username[0].toUpperCase()} + +
+
+
{username}
+
+ Wants to chat +
+
+
+
+ + +
+
+ ))} +
+ )} + {/* Incoming Chats (not in contacts) */} {Object.keys(activeChats).filter(username => !contacts.includes(username) && activeChats[username].status === 'connected').length > 0 && (
- Incoming Chats ({Object.keys(activeChats).filter(username => !contacts.includes(username) && activeChats[username].status === 'connected').length}) + Active Chats ({Object.keys(activeChats).filter(username => !contacts.includes(username) && activeChats[username].status === 'connected').length})
{Object.keys(activeChats) .filter(username => !contacts.includes(username) && activeChats[username].status === 'connected') @@ -1005,7 +1134,7 @@ export default function App() {
{contact}
- Connected (incoming) + Connected
@@ -1053,15 +1182,51 @@ export default function App() { {hasActiveChat ? 'Chatting' : isOnline ? 'Online' : 'Offline'} + {hasActiveChat && ( + + )} );